ACID
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性(Consistency)
一致性是指事务必须使数据库从一个一致的状态变到另外一个一致的状态,也就是执行事务之前和之后的状态都必须处于一致的状态。
隔离性(Isolation)
隔离性是指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对于数据库中的数据改变就是永久性的,即便是在数据库系统遭遇到故障的情况下也不会丢失提交事务的操作。
两段锁协议
两段锁协议是指所有事务必须分为两个阶段:
获得封锁(扩展阶段):在这阶段,事务可以申请获得任何数据项上的任何锁的类型,但是不能释放任何锁。
释放封锁(收缩阶段):在这阶段,事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁。
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行。
DBMS 事务遵守两段锁协议是为了实现并发调度的可串行化,在数据库恢复和备份时候很重要,但是二段锁相对于一段锁不能避免死锁。
事务隔离级别
脏读(Dirty Read)
一个事务处理过程里读取了另一个未提交的事务中的数据。
不可重复读(NonRepeatable Read)
对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询的间隔期间,另外一个事务修改并提交了该数据。
幻读(Phantom Read)
在一个事务中读取到了别的事务插入的数据,导致前后不一致。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
- 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
- 提交读(Read Committed):只能读取到已经提交的数据。Oracle 等多数数据库默认都是该级别 (不重复读)。
- 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB 默认级别。在 SQL 标准中,该隔离级别消除了不可重复读,但是还存在幻象读(但是 innodb 的实现中,在 RR 级别,通过 MVCC 和 Next-key 锁解决了快照读和当前读的幻读问题)。
- 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
修改事务隔离级别
set [global | session] transaction isolation level 隔离级别名称
- 设置默认级别是指当前 session 的下一个事务。
- 设置 session 级别是指当前 session 以后的所有事务。
- 设置 global 级别是指对之后的所有 session,不包括当前 session。
实例
1 | CREATE TABLE `user` ( |
如果一个条件无法通过索引快速过滤,存储引擎层面就会将所有记录加锁后返回,再由 MySQL Server 层进行过滤。
实际使用过程当中,MySQL 做了一些改进,在 MySQL Server 过滤条件,发现不满足后,会调用 unlock_row 方法,把不满足条件的记录释放锁 (违背了二段锁协议的约束)。
未提交读(Read uncommitted)
会出现脏读。
session1 | session2 |
---|---|
begin; | begin; |
update user set name = ‘godtail1’ where score = 5; | - |
- | select * from user; |
commit; | - |
在 session1 事务未提交的时候,session2 获取到了修改后的数据(脏读),insert 操作也一致。
提交读(Read Committed)
不会出现脏读,但是不可重复读。
session1 | session2 |
---|---|
begin; | begin; |
- | select * from user where score = 5; |
update user set name = ‘godtail1’ where score = 5; | - |
commit; | - |
- | select * from user where score = 5; |
- | commit; |
在 session1 的事务提交后,第二次查询结果和第一次查询结果不一致。
可重复读(Repeatable read)
在同一个事务中,多次执行快照读和当前读的结果是一致的。
session1 | session2 |
---|---|
begin; | begin; |
- | select * from user [for update] where score = 5; |
update user set name = ‘godtail1’ where score = 5; | - |
commit; | - |
- | select * from user [for update] where score = 5; |
- | commit; |
可串行化(Serializable)
This level is like REPEATABLE READ, but InnoDB implicitly converts all plain SELECT statements to SELECT … LOCK IN SHARE MODE if autocommit is disabled. If autocommit is enabled, the SELECT is its own transaction. It therefore is known to be read only and can be serialized if performed as a consistent (nonlocking) read and need not block for other transactions. (To force a plain SELECT to block if other transactions have modified the selected rows, disable autocommit.)
在可串行化隔离级别中,SELECT 将会被转换为 SELECT … LOCK IN SHARE MODE,如果需要在 SELECT 的时候阻塞其他事务修改相关行,需要设置 AUTOCOMMIT=0。
Innodb 在解决幻读的方案
快照读
select,没有 lock in share mode 或 for update。
当前读
insert、update、delete、select..in share mode、select..for update。
悲观锁
在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。
乐观锁
乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
针对快照读的 MVCC
在 InnoDB 中,会在每行数据后添加两个额外的隐藏的值来实现 MVCC,这两个值一个记录这行数据的创建版本号,另外一个记录这行数据的过期版本号。
事务的版本号是递增的,没开启一个新的事务会增加。
- SELECT:获取创建版号本小于或等于当前版本号的,剔除删除版本号为空,或者大于当前版本号的记录。
- INSERT:保存当前的版本号到创建版本号。
- DELETE:保存当前的版本号到删除版本号。
- UPDATE:保存当前的版本号到创建版本号和删除版本号。
针对当前读的 Next-key 锁
next-key 锁是行锁+间隙锁。
有几个需要注意的地方
- 针对唯一索引进行等值查询的时候,只需添加行锁,不需要加间隙锁。
- 如果使用 INSERT … ON DUPLICATE KEY UPDATE,会使用排他 next-key 锁,否则使用共享 next-key 锁。
TODO: 关于间隙锁的区间范围,待实践确认