相关博客:事务相关概念
本章示例表及数据如下:
- 数据库:
- 默认数据
示例
start transaction
:开启事务rollback
:回滚事务commit
:提交事务
示例1:演示事务提交
示例2:演示事务回滚
事务隔离级别
事务隔离级别 | 名称 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
read-uncommitted | 读未提交,也叫脏读 | 是 | 是 | 是 |
read-committed | 不可重复读,也叫读已提交 | 否 | 是 | 是 |
repeatable-read | 可重复读,默认级别 | 否 | 否 | 否 |
serializable | 串行化 | 否 | 否 | 否 |
说明:
- 隔离级别为读已提交时,写数据只会锁住相应的行。
- 读已提交解决了脏读,但是两次读取的结果可能会不一致。
- 隔离级别可重复读解决了多次查询结果都是一样的问题,但是如果事务执行期间有其他事务插入新数据,此时会产生幻读。
- 隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有 索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
- 事务隔离级别为串行化时,读写数据都会锁住整张表
- 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
查看MySQL默认的事务隔离级别
示例3:
注意:在MySQL8之前的命令为:select @@tx_isolation
读未提交(read uncommitted)
- 两个事务,一个事务未提交的数据,另一个事务可以读取到,这里读取到的数据叫做“脏数据”。
- 这种隔离级别最低,这种级别一般是在理论上存在,实际应用中数据库隔离级别一般都高于该级别。
示例4:
第一步:打开客户端A,设置当前事务模式为read uncommitted(未提交读),查询tb_balance表zhangsan的money的初始值:
第二步:在客户端A的事务提交之前,打开另一个客户端B,更新tb_balance表zhangsan的money的值:
第三步:此时,虽然客户端B的事务还没提交,但是在客户端A已经可以查询到B已经更新的数据:
第四步:客户端B的事务因为某种原因回滚,它的所有的操作都将会被撤销。
第五步:客户端A查询到的数据其实就是脏数据了:
在客户端A执行执行更新语句zhangsan的money没有变成300,居然是400,数据不一致!
读已提交(read committed)
- Oracle默认隔离级别
- 两个事务,数据只有一个事务提交了后,另一个事物才能读取到,即:对方事物提交之后的数据,当前事物才能读取到。
- 读已提交隔离级别高于读未提交。这种级别可以避免“脏数据”,但是会导致“不可重复读取”,存在幻读问题
示例5:
第一步:打开客户端A设置当前的事务隔离级别为read committed (未提交读)查询表tb_balance的所有记录:
第二步: 将客户端B的事务的隔离级别调整为read committed级别,并开启事务,更新表tb_balance
第三步:此时,客户端B的事务还没有提交,客户端A是不能查询到B已经更新的数据,解决了脏数据的问题:
第四步:客户端B提交事务
第五步:客户端A执行查询,结果就回发现与上一次查询结果不同,就产生了不可重复读的问题
可重复读(repeatable read)
- MySQL默认隔离级别
- 两个事务,一个事务提交之后的数据,另一个事务读取不到
- 这种隔离级别高于读已提交,可以避免“不可重复读取”,但是会导致“幻像读”
示例6:
第一步:打开客户端A将事务的隔离级别调整为repeatable read 级别
第二步: 打开客户端B将事务的隔离级别调整为repeatable read级别,并对表tb_balance进行更新,但并未提交事务
第三步:在客户端A查询表tb_balance的所有记录,没有出现脏读的情况。
第四步:客户端B修改数据后提交了事务。
第五步:在客户端A查询表tb_balance的所有记录,没有出现不可重复读的情况。
第六步:在客户端A执行更新语句发现数据的一致性得到保证。
之所以数据的一致性得到保证,原因是:在可重复读的隔离级别下,MySQL采用的是MVCC机制,select 操作不会更新版本号,是快照读(历史版本);而insert、update和delete会更新版本号,是当前读(当前版本)。
第七步:打开客户端B,尝试更新zhangsan的数据,会失败,这说明可重复隔离读级别下MySQL8已经不出现幻读的情况了。
串行化(serializable)
Serializable完全串行化的读,每次读都需要获得表级共享锁,读写操作相互互斥,这样可以更好的解决数据一致性的问题,但是同样会大大的降低数据库的实际吞吐性能。所以该隔离级别因为并发性比较低、损耗太大,一般很少在开发中使用。
- MySQL中事务隔离级别为serializable时会锁表,因此不可能出现脏读数据、不可重复读、幻读的情况。
- 两个事务,当一个事务操作数据库时,另一个事务只能排队等待
- 这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,多个事务之间串行,而不并发。
- 这种隔离级别很少使用,吞吐量太低,用户体验差
第一步:在客户端A中设置当前事务模式为Serializable,同时开启事务:
第二步:在客户端B中设置当前事务模式为serializable,插入一条记录,此时表tb_balance已经被锁定。
因为已经发生了锁表,所以此时在客户端中再次执行查询会报错: