mysql——索引事务和JDBC编程
1. 索引
索引是数据库中用于加速查询的数据结构,类似于书籍的目录。
索引的作用
大幅提高查询速度
加速表连接操作
保证数据唯一性(唯一索引)
优化排序和分组操作
1. 创建索引
create index 索引名 on 表名(字段名);
show index from 表名;
3. 删除索引
drop index 索引名 on 表名;
1. 按数据结构分类
如果索引不存在,当我们想要查找某个数据时,时间复杂度为O(N)
-
特点:
-
基于哈希表实现
-
精确匹配效率极高(O(1)时间复杂度)
-
不支持范围查询和排序
-
-
适用场景:等值查询(=, IN)
-
存储引擎支持:MEMORY引擎(默认)、InnoDB(自适应哈希索引)
-
特点:默认索引类型,多路平衡查找树
每个节点上有 M 个key ,划分出了 M+1 个区间
进行查询时,直接从根节点出发,判断当前要查的节点在哪个区间中,决定下一步往哪里走
进行 插入/删除操作时可能涉及到节点的拆分和合并
-
适用场景:
-
全值匹配(=)
-
范围查询(>, <, BETWEEN)
-
前缀匹配(LIKE 'abc%')
-
排序(ORDER BY)
-
分组(GROUP BY)
-
无法使用后缀匹配(like '%abc')
-
存储引擎支持:InnoDB、MyISAM、MEMORY等
特点:1. B+ 树也是一个N叉搜索树,每个节点上有 M 个key ,划分出了 M 个区间2. 父节点上的每个key都会以最大值的身份存储在子节点中,所以叶子节点这一层包含了整个树的数据全集3. B+ 树会使用链表将叶子节点串联起来(此时就可以非常方便的完成数据集合的遍历,也可以很方便的从数据集合中按照范围取出一个子集)
优点:1. N 叉搜索树,树的高度是有限的,降低 io 的次数2. 擅长范围查询3. 所有查询最终都要落到叶子节点,查询和查询之间的时间开销是稳定的4. 由于叶子节点是全集,会把行数据只存储在叶子节点上,非叶子节点只存储一个用来排序的key5. 在进行查询时,可以将硬盘中的非叶子节点加载到内存中,整体查询的比较过程就可以在内存中进行。
2. 事务
A给B转2000块钱,在转账过程中,A的账户扣除2000块钱,此时突然断网或数据库挂了,B的账号没有增加2000块钱,这是一个重大错误。
使用:( 1 )开启事务: start transaction;( 2 )执行多条 SQL 语句( 3 )回滚或提交: rollback/commit;说明: rollback 即是全部失败, commit 即是全部成功。
2. 一致性:事务执行前后,数据库从一个一致状态变到另一个一致状态
-
数据完整性约束不被破坏
-
业务规则保持一致
数据隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
---|---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 | 可读取未提交数据 |
READ COMMITTED | 不可能 | 可能 | 可能 | 只能读取已提交数据(读加锁) |
REPEATABLE READ | 不可能 | 不可能 | 可能 | 同一事务多次读取结果一致(MySQL默认) (读写都加锁) |
SERIALIZABLE | 不可能 | 不可能 | 不可能 | 完全串行化执行 |
1. 查看和设置隔离级别
-- 查看当前隔离级别
SELECT @@transaction_isolation;-- 设置会话级隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;-- 设置全局级隔离级别(需重启生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2. 隔离级别问题演示
-
锁机制:
-
共享锁(S锁):读锁,其他事务可读不可写
-
排他锁(X锁):写锁,其他事务不可读写
-
(1) 脏读(Dirty Read)
一个事务A 在写数据的过程中,事务B读取了该数据,之后事务A又修改了该数据,导致事务B之前读取的数据时一个无效/过时的数据(也称为 脏数据)
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 未提交-- 会话2(可以读取到未提交的数据)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM accounts WHERE user_id = 1; -- 看到未提交的修改
解决方法:
在事务A进行写操作时加锁(即在事务A进行写操作时,事务B不可以进行读操作)
(2) 不可重复读(Non-repeatable Read)
事务A提交一份数据后,事务B进行多次读取,在事务B读取数据过程中,事务A又修改了该数据并提交,导致事务B读取的前后数据不同。
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE user_id = 1; -- 第一次读取-- 会话2
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
COMMIT;-- 会话1(同一事务内两次读取结果不同)
SELECT balance FROM accounts WHERE user_id = 1; -- 第二次读取
COMMIT;
解决方法:
在事务B进行读操作时加锁(即在事务B读操作时,事务B不可以进行写操作)
在事务A进行写操作时加锁(即在事务A进行写操作时,事务B不可以进行读操作)
(3) 幻读(Phantom Read)
事务A提交一份数据后,事务B进行多次读取,在事务B读取数据过程中,事务A又提交了一份数据,导致事务B读取的前后结果集不同。
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM accounts WHERE balance > 1000; -- 返回2条记录-- 会话2
INSERT INTO accounts VALUES(3, '王五', 1500);
COMMIT;-- 会话1(同一事务内相同查询返回不同行数)
SELECT * FROM accounts WHERE balance > 1000; -- 返回3条记录
COMMIT;
解决方法:
引入串行化方式
总结:
( 1 )对于插入、删除数据频率高的表,不适用索引( 2 )对于某列修改频率高的,该列不适用索引( 3 )通过某列或某几列的条件查询频率高的,可以对这些列创建索引
3. JDBC 编程
配置:
1. 从中央仓库下载mysql的jdbc驱动包(jar)
2. 将 jar 引入到项目中(拷贝,add as library)
高内聚:把相同/相关联的功能放在一起
高耦合:两个模块关联关系紧密,一个模块修改对另一个模块有很大影响
我们要求程序 高内聚,低耦合
jdbc开发案例
javax.sql jdbc 包
127.0.0.1 是一个特殊的IP地址,叫做“环回IP”(lookback)
每个机器上都有环回IP(127.0.0.1),只要把消息发送给这个IP,就等于发给自己
由于我们的jdbc程序和mysql服务器在同一个主机上,使用环回IP
import com.mysql.cj.jdbc.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;public class Domo {public static void main(String[] args) throws SQLException {Scanner scan = new Scanner(System.in);System.out.print("请输入id:");int id = scan.nextInt();scan.nextLine();System.out.print("请输入姓名:");String name = scan.nextLine();//1.创建DataSource(抽象接口)//DataSource 描述“数据源头”,即数据库服务器所在位置DataSource dataSource = new MysqlDataSource();//找到mysql服务器//url(网址):网络上具体资源位置((MysqlDataSource) dataSource).setURL("jdbc:mysql://127.0.0.1:3306/school?characterEncoding=utf8&useSSL=false");//向下转型//认证用户名//root 管理员mysql默认自带用户((MysqlDataSource) dataSource).setUser("root");//认证密码((MysqlDataSource) dataSource).setPassword("123");//2. 和数据库服务器建立连接//getConnection()是jdbc中常见异常,如果执行sql或操作数据库过程中出现问题,一般都抛出这个异常//connection连接Connection connection=dataSource.getConnection();//3. 构造sql(代码中的sql不需要写;)String sql = "insert into student values(1,'lh'),(id,'"+ name +"'),(?,?)";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1,id);preparedStatement.setString(2,name);//4. 把sql发送给服务器,返回值是一个整数,表示影响的行数int n = preparedStatement.executeUpdate();System.out.println(n);//5. 释放资源,关闭连接(先申请资源,后释放)preparedStatement.close();connection.close();}
}
Prepared :准备好的/预处理的
Statement: 语句
先解析检查 sql ,看看sql是否有问题,解析完毕后,会得到结构化数据,直接把解析好的结构化数据发送给数据库,服务器就省下了这部分解析工作
如果直接把字符串格式的 sql 发给mysql 服务器,mysql 服务器就需要对 sql 解析和校验,但 mysql 服务器是服务于多个客户端,此时总的开销和总的负担就很大。
import com.mysql.cj.jdbc.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class Domo2 {public static void main(String[] args) throws SQLException {//建立DataSouece(抽象接口)DataSource dataSource = new MysqlDataSource();((MysqlDataSource) dataSource).setURL("jdbc:mysql://127.0.0.1:3306/school?characterEncoding=utf8&useSSL=false");((MysqlDataSource) dataSource).setUser("root");((MysqlDataSource) dataSource).setPassword("123");//2. 和数据库服务器建立连接Connection connection = dataSource.getConnection();//3. 构造sqlString sql = "select * from student";PreparedStatement statement = connection.prepareStatement(sql);//4. 执行sql//ResultSet 表示查询的结果集合ResultSet resultSet = statement.executeQuery();//5. 遍历结果集合while(resultSet.next()){int id = resultSet.getInt("id");String name = resultSet.getString("name");System.out.println("id = "+id +", name ="+ name);}//释放资源resultSet.close();statement.close();connection.close();}
}