提示:以下是本篇文章正文内容,MySQL 系列学习将会持续更新
一、事务
- 在数据库里面,我们希望有些操作能够以原子的方式进行,要么都能执行成功,要么就都不执行,也就是只能是一个整体的被执行,这样的一组具有原子性的操作我们就称之为事务。
- 我们的 MySQL 支持 9 种数据库引擎,但只有默认的
Innodb
引擎支持事务功能。
1.1 事务特征
- 原子性 (atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性 (consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
- 隔离性 (isolation):当多个事务对同一资源同时操作时,一个事务的执行不能被其他事务干扰。这里的同时只是宏观上的表现,实际上也就是微观上同一时刻只有一个事务在执行,而其它事务是在等待中。
- 持久性 (durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,即便系统故障也不会丢失。
1.2 隔离级别
更追求隔离性(数据更正确) | ----------------------------- | ------------------------------------------------ | ---------------------------------> | 更追求并发性(性能更高) |
---|---|---|---|---|
(可串行性)serializable | (快照读)snapshot_read | (可重复读)repeatable_read | (读已提交)read_committed | (读未提交)read_uncommitted |
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。 | 不是标准中存在的隔离级别,目前来说,没有副作用。 MySQL中的可重复读就是实际上的快照读。因为MVCC机制解决了幻读。 | 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这会导致幻读:当用户修改某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有一条未修改的数据“幻影” 。 | 只能读取其它事务已经提交的内容,存在不可重复读问题:一个事务多次读取同一数据可能会得到多个不同的结果 。 | 能够读取到其它事务中未提交的内容,存在脏读问题。读取未提交的数据,也被称之为脏读 。 |
我们可以修改隔离级别:
set session transaction isolation level read uncommitted;
1.3 开启事务
①SQL开启事务
-- 开启事务
start transaction; / begin;
SQL1;
SQL2;
rollback; -- 主动回滚
-- 开启事务
start transaction; / begin;
SQL1;
SQL2;
SQL3;
commit; -- 提交事务,失败也会回滚
②JDBC使用事务
// 要使用事务,在同一个事务中,操作 sql1 和 sql2,意味着必须在一条 Connection 完成
try (Connection c = DBUtil.connection()) {
// connection 中有一个自动提交(autocommit)的属性,默认情况下是 true(开启)
// 开启状态下,意味着,每一条 sql 都会被独立的视为一个事务
// 我们要让 sql1 和 sql2 看作整体,只需要关闭 connection 的自动提交
c.setAutoCommit(false);
// 此时就可以手动的控制事务的结束位置,并且需要手动提交
try (PreparedStatement ps = c.prepareStatement(sql1)) {
ps.executeUpdate();
}
try (PreparedStatement ps = c.prepareStatement(sql2)) {
ps.executeUpdate();
}
// 由于我们关闭了自动提交了,所以,所有的修改还没有真正地落盘
c.commit(); // 只有加上这句话,才表示事务被提交了(数据真正落盘了)
}
二、锁机制
我们知道在可重复读的级别下,MySQL 在一定程度上解决了幻读问题:
- 在快照读(不加锁)读情况下,mysql 通过 MVCC (多版本并发控制) 来避免幻读。
- 在当前读(加锁)读情况下,mysql 通过 next-key 来避免幻读。
2.1 读锁、写锁
从对数据的操作类型上来说,锁分为读锁和写锁:
- 读锁:也叫共享锁,当一个事务添加了读锁后,其他的事务也可以添加读锁或是读取数据,但是不能进行写操作,只能等到所有的读锁全部释放。
- 写锁:也叫排他锁,当一个事务添加了写锁后,其他事务不能读不能写也不能添加任何锁,只能等待当前事务释放锁。
2.2 全局锁、表锁、行锁
从锁的作用范围上划分,分为全局锁、表锁和行锁:
①全局锁:锁作用于全局,整个数据库的所有操作全部受到锁限制。
flush tables with read lock;
②表锁:锁作用于整个表,所有对表的操作都会收到锁限制。
lock table 表名称 read; -- 读锁
lock table 表名称 write; -- 写锁
-- 除了手动释放锁之外,当我们的会话结束后,锁也会被自动释放。
unlock tables;
③行锁:锁作用于表中的某一行,只会通过锁限制对某一行的操作(仅InnoDB支持)
-- 添加读锁(共享锁)
select * from 表名 where ... lock in share mode;
-- 添加写锁(排他锁)
select * from 表名 where ... for update;
2.3 记录锁、间隙锁、临键锁
我们知道 InnoDB 支持使用行锁,但是行锁比较复杂,它可以继续分为多个类型,详细可查看文章:MySQL的锁机制 - 记录锁、间隙锁、临键锁
①记录锁(Record Locks): 仅仅锁住索引记录的一行,在单条索引记录上加锁。Record lock 锁住的永远是索引,而非记录本身。所以说当一条 sql 没有走任何索引时,那么将会在每一条聚合索引后面加写锁,这个类似于表锁,但原理上和表锁应该是完全不同的。
- id 列必须为唯一索引列或主键列,否则加的锁就会变成临键锁。
- 同时,查询语句必须为精准匹配(=),不能为 >、<、like等,否则也会退化成临键锁。
②间隙锁(Gap Locks): 仅仅锁住一个索引区间(开区间)。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。比如在 1、2 中,间隙锁的可能值有 (-∞, 1),(1, 2),(2, +∞),间隙锁可用于防止幻读,保证索引间的不会被插入数据。
- 对于主键索引:精准查询存在列,只会产生记录锁;精准查询不存在列,会产生记录锁和间隙锁;范围查询会产生间隙锁。
- 对于普通索引:不管是何种查询,只要加锁,都会产生间隙锁。
③临键锁(Next-Key Locks): Record lock + Gap lock
,左开右闭区间。默认情况下,InnoDB 正是使用 Next-key Locks 来锁定记录(如select … for update
)。
它还会根据场景进行灵活变换:
场景 | 转换 |
---|---|
使用唯一索引进行精确匹配,但表中不存在记录 | 自动转换为 Gap Locks |
使用唯一索引进行精确匹配,且表中存在记录 | 自动转换为 Record Locks |
使用非唯一索引进行精确匹配 | 不转换 |
使用唯一索引进行范围匹配 | 不转换,但是只锁上界,不锁下界 |
总结:
提示:这里对文章进行总结:
本文是MySQL的学习,先学习了事务的四大特征、隔离级别,如何开启事务。又学习了锁机制,认识了读写锁、行表锁、记录锁等。之后的学习内容将持续更新!!!