e. 脏数据展现:在客户端A执行更新语句update account set balance=balance-50 where id=1,lilei的balance没有变成350,而是400,数据��一致,因为在这过程中,并不知道客户端B会话回滚了,行数据实际上是450,450-50=400,可以采用读已提交的隔离级别。
② 读已提交:
a. 打开一个客户端A,并设置当前事务隔离级别为read committed(读已提交),set tx_isolation='read-committed',查询account表的初始值:
b. 在客户端A的事务提交之前,打开另一个客户端B,并设置当前事务隔离级别为read committed,更新account表:
c. 这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读的问题:
d. 客户端B的事务提交:
e. 客户端A执行与上一步相同的查询,结果与上一步不一致,即产生了不可重复读的问题。
③ 可重复读
a. 打开一个客户端A,并设置当前事务隔离级别为repeatable read(可重复读),set tx_isolation='repeatable-read',查询account表的初始值:
b. 在客户端A的事务提交之前,打开另一个客户端B,并设置当前事务隔离级别为repeatable read,更新account表:
c. 在客户端A查询account表的所有记录,与步骤a的查询结果一致,没有出现不可重复读的问题。
d. 在客户端A执行update account set balance=balance-50 where id=1,balance没有变成350-50=300,lilei的balance值用的是步骤b中的300来算的,所以是250,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。
e. 重新打开客户端B,插入一条新数据后提交:
f. 在客户端A查询account表的所有记录,没有查出新增数据,所以没有出现幻读:
g. 验证幻读:在客户端A执行update account set balance=666 where id=4;能更新成功,再次查询能查到客户端B新增的数据:
④ 串行化
a. 打开一个客户端A,并设置当前事务隔离级别为serializable(串行化),set tx_isolation='serializable',查询account表的初始值:
b. 打开一个客户端B,并设置当前事务隔离级别为serializable,插入一条记录报错,表被锁了插入失败,MySQL中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,但这种隔离级别并发性能极低,开发中很少会用到。
提问:MySQL默认级别是repeatable-read,有办法解决幻读问题吗?
间隙锁在某些情况下可以解决幻读问题。要避免幻读可以用间隙锁在session_1下面执行update account set where id>3 and id<=20,则其他session没法插入这个范围内的数据。
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。
无索引行锁升级为表锁:varchar如果不加 ' ',将导致系统自动转换类型,行锁变表锁,例如:update table set name=Zeki where id=1,这个语句会导致行锁变表锁,其他session无法对这个表做操作,5.7之后的版本这样写SQL会报错。