事物需要符合ACID特性。
原子性:事物操作具有“原子性”,同一事物中的操作,全部成功或者全部失败。
一致性:保证数据的一致性。
隔离性:事物之前的操作具有隔离性,不能造成相互之间的影响。
持久性:事物提交的数据需要进行存储。
1.1 处理并发事务带来的问题
(1)脏读:在不同事物下,当前事物可以读到另一个事物未提交的数据,即读到了脏数据,违反了事物的隔离性。由于另一个事物最终是否能正常提交未知,所以可能会导致数据的不一致。
设置read committed(RC)以上隔离级别可解决问题。
(2)不可重复读:在同一个事物中多次读取同样记录的结果不一致的。
设置repeatable read (RR)隔离级别可解决问题。
(3)幻读:当前事物在读取某个范围内的记录时,另一个事物在该范围内插入了新记录,当前事物再次读取该范围内的记录时,会产生幻行。
设置serializable隔离级别可解决问题。InnoDB的RR隔离级别通过MVCC和Next-Key Lock锁算法也可解决幻读问题。
(4)丢失更新:针对同一条数据库表记录,不同事物同时分别更新该记录的同一个字段值,就会发生数据更新丢失问题。
解决方案:针对记录的有效标记字段(主键等)使用分布式锁;使用serializable隔离级别。
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
1.2 Mysql事物隔离的实现技术
-
MVCC
多版本并发控制(MVCC)是通过保存数据在某个时间点的快照来实现。不管事物执行时间多久,每个事物看到的结果都是一致的,Mysql InnoDB存储引擎就是在repeatable read隔离级别使用MVCC实现的可重复读。
InnoDB的MVCC通过在每行记录的后面保存两个隐藏列实现,一个是行的创建时间,一个是删除时间,创建时间和删除时间是事物的版本号,每个事物开始的时候都会产生一个自动递增系统版本号。InnoDB检查每行数据,确保他们符合两个标准:
(1)SELECT<1>InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行;
<2>行的删除操作的版本一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前,行没有被删除
符合了以上两点则返回查询结果。
(2)INSERT
InnoDB中新增行记录的创建ID为当前事物系统版本号,删除ID无。
(3)DELETE
InnoDB中删除行记录的创建ID不改变,删除ID为当前事物系统版本号。
(4)UPDATE
InnoDB更新记录原理是复制数据。这个新行的创建ID为当前事物系统版本号;旧数据的删除ID为当前事物系统版本号。
MVCC只有在read committed和repeatable read两个隔离级别下工作。read committed级别总是读取已提交的数据的最新的创建ID;repeatable read的读取规则见上面内容。
1.3 事物的隔离级别及实现
原始数据:每次演示后数据都重置到原始数据。
id | name |
---|---|
1 | test1 |
2 | test2 |
-
read uncommitted (RU)读未提交:
定义:能读取其他事物未提交的数据。会导致脏读、不可重复读、幻读问题。
实现原理:其他事物可以直接读取另一个事物在缓存池中的数据。演示:
time A B 1 set session transaction isolation level read uncommitted;
select @@tx_isolation;
set autocommit = 0;2 set session transaction isolation level read uncommitted;
select @@tx_isolation;
set autocommit = 0;3 begin; begin; 4 select name from user where id=1;
test15 update user set name = ‘updateB1’ where id =1; 6 select name from user where id=1;
updateB17 rollback; 8 select name from user where id=1;
test1 -
read committed(RC) 读已提交:
定义:只能读取其他事物已经提交的数据,会导致不可重复读、幻读问题。演示:
time A B 1 set session transaction isolation level read committed;
select @@tx_isolation;
set autocommit = 0;2 set session transaction isolation level read committed;
select @@tx_isolation;
set autocommit = 0;3 begin; 4 begin; 5 select name from user where id=1;
test16 update user set name = ‘updateB1’ where id =1; 7 select name from user where id=1;
test18 commit; 9 select name from user where id=1;
test1 -
repeatable read (RR)可重复读:
定义:在同一个事物中多次读取数据结果都是一样(一般是指对有确切相等条件的查询语句,多次查询结果相同),是mysql数据库InnoDB存储引擎默认的隔离级别,InnoDB存储引擎在RR隔离级别下,使用MVCC和Next-Key Lock锁算法避免出现幻读问题,达到了串行化的隔离级别。(1)可重复读演示:
time A B 1 set session transaction isolation level repeatable read;
select @@tx_isolation;
set autocommit = 0;2 set session transaction isolation level repeatable read;
select @@tx_isolation;
set autocommit = 0;3 begin; 4 begin; 5 select name from user where id=1;
test1
select name from user where id=2;
test2select name from user where id=1;
test1
select name from user where id=2;
test26 update user set name = ‘updateA1’ where id =1; 7 update user set name = ‘updateB2’ where id =2; 8 select name from user where id=1;
updateA1
select name from user where id=2;
test2select name from user where id=1;
test1
select name from user where id=2;
updateB29 commit; 10 select name from user where id=1;
updateA1
select name from user where id=2;
test2set autocommit = 1;
select name from user where id=1;
test1
select name from user where id=2;
updateB211 commit; 12 set autocommit = 1;
select name from user where id=1;
updateA1
select name from user where id=2;
updateB2set autocommit = 1;
select name from user where id=1;
updateA1
select name from user where id=2;
updateB2说明及实现原理:
<1> 6、7两步两个事物分别更新了记录,但第8步的查询结果证明只有各自的事物才能看到本事物更新的记录。
<2> 9、10步,因为A事物未提交,所以B事物也看不到A事物的更新记录。虽然B事物已经提交,但A事物仍然看不到B事物的更新记录。因为B事物更新了id为2的记录的name字段值,底层操作是将id为2旧的行记录的删除ID赋值为B事物的系统版本号2(A事物先提交,A的系统版本号为1);新的记录的创建ID赋值为B事物的系统版本号2。根据MVCC的规则和下表的内容,A事物读取id为2的数据时,老数据符合创建ID小于A的版本号,删除ID大于A的版本号,所以查询才能选到该记录;新数据的创建ID大于A的版本号,不符合筛选条件。
注:如果都符合条件,筛选创建ID最大的数据。数据 id name 创建ID(隐藏列) 删除ID(隐藏列) 老数据 2 test2 不变( 0) B事物的系统版本号(2) 新数据 2 updateB2 B事物的系统版本号(2) 无 如果B事物先提交结果也是可重复读,具体原因未仔细研究。???
注:事物中的“更新”操作会加排它锁(X锁),另一个事物的更新操作是需要等待该事物提交的;读操作是读取记录的快照,快照通过undo段完成,不需要等待,但根据MVCC的不同隔离级别下的读取规则不同,读取的数据会不同。MVCC可提高数据库的并发性。
<3> 11、12 步,两个事物都提交,所以看到的记录是两个事物更新的记录。(2)解决幻读的演示:
time A B 1 set session transaction isolation level repeatable read;
select @@tx_isolation;
set autocommit = 0;2 set session transaction isolation level repeatable read;
select @@tx_isolation;
set autocommit = 0;3 begin; 4 begin; 5 select name from user ;
test1
test2select name from user ;
test1
test26 INSERT INTO user(id, name) VALUES(3, ‘test3’); 7 select name from user;
test1
test2select name from user;
test1
test2
test38 commit; 9 select name from user;
test1
test2select name from user;
test1
test2
test311 commit; 12 select name from user;
test1
test2
test3select name from user;
test1
test2
test3Mysql 的InnoDB存储引擎的RR隔离级别通过Next-Key Lock锁算法可解决幻读问题。
-
serializable 串行化:
定义:解决幻读问题,但性能稍差。因为隔离级别越高,请求的锁越多,保持锁的时间越长,锁的竞争越激烈,可能对并发性能造成影响。
实现原理:mysql的serializable事物隔离级别,InnoDB存储引擎会对每个SELECT语句后自动增加LOCK IN SHARE MODE,即每个读取操作加了共享锁。
1.4 问题答疑
-
如何查看数据库的当前事物隔离级别?
select @@tx_isolation;
—— REPEATABLE-READ -
如何修改事物隔离级别?
set session transaction isolation level read uncommitted;
—— READ-UNCOMMITTED -
Next-Key Lock锁算法的实现原理?
-
事物隔离性的实现细节?undo段实现细节?
1.5 总结
- read uncommintted 隔离级别可以读取事物未提交的数据;
- read commintted隔离级别读取其他事物已经提交的数据,在MVCC中读取规则是读取创建时间是最新版本号的记录;
- repeatable read隔离级别读取其他事物已经提交的数据,在MVCC中读取规则是读取创建时间版本号小于等于当前事物版本号并且删除时间未定义或大于当前事物版本号的记录,以此来实现可重复读;
repeatable read隔离级别通过Next-Key Lock锁算法可解决幻读问题; - 数据库的“更新”操作会加锁,另一个事物中如果对该记录也进行更新操作需要等待;查询操作由于MVCC的实现,不需要等待(在serialable隔离级别下读取也需要等待)。