面试题:Mysql的隔离级别及实现
MySQL的隔离级别
读未提交:可能存在脏读、不可重复读、幻读的问题
读已提交:不存在脏读,存在不可重复读、幻读问题
可重复读:不存在脏读、不可重复读,存在幻读问题
串行化:不存在脏读、不可重复读、幻读问题
脏读、不可重复、幻读
- 脏读:一个事务读取到了其他事务未提交的数据
事务2对某条数据进行了修改,但是还未提交,此时数据库的数据已经修改,事务1读到了该数据。 - 不可重复读:一个事务两次读取同一条数据返回结果不同
事务1中有多条读取数据库某个数据A的请求,第一次读取得到张三的专业为“软件工程”,此时事务2进行了修改,专业修改为“计算机科学”,并且提交,事务1再次进行读取时,发现张三的专业发生了变化。 - 幻读
事务1查询某个范围的记录时,数据量只有10条,此时事务2新增了一条该范围内的数据,事务1再次查询时发现数据量出现了不一致。
通过加锁的方式实现不同的隔离级别
- 读未提交:不加锁
- 读已提交:读操作加行级共享锁,每次读取完毕后释放。写操作加行级排他锁,事务提交后释放。
事务1读取时加共享锁,此时写操作被阻塞,事务1完成一次读取操作后,事务2加排他锁并对数据进行修改,事务2完成后释放排他锁,事务1继续查询,发现两次查询结果不一致,存在不可重复读问题。 - 可重复读:读操作加行级共享锁,事务提交后释放。写操作加行级排他锁,事务提交后释放
- 串行化:读操作加Next-Key锁,事务提交后释放。写操作加行级排它锁,事务提交后释放
数据中存在 c=3,c=4,c=7,c=12四条数据,间隙为(-∞,3),(4,7),(7,12),(12,+∞),事务1读取5<c<10的数据时,会对间隙(4,7),(7,12)加锁,此时如果想插入数据c=6会被阻塞,也就解决了幻读的问题。
通过MVCC实现不同的隔离级别
Read View的几个属性
-
trx_ids: 当前系统活跃(未提交)事务版本号集合。
-
low_limit_id: 创建当前read view 时“当前系统最大事务版本号+1”(高水位)
-
up_limit_id: 创建当前read view 时“系统正处于活跃事务最小版本号”(低水位)
-
creator_trx_id: 创建当前read view的事务版本号;
Read View判断是否可见
-
db_trx_id < up_limit_id || db_trx_id == creator_trx_id(显示)
如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示。
或者数据的事务ID等于creator_trx_id ,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。 -
db_trx_id >= low_limit_id(不显示)
如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。如果小于则进入下一个判断 -
db_trx_id是否在活跃事务(trx_ids)中
不存在:则说明read view产生的时候事务已经commit了,这种情况数据则可以显示。
已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。
实现不同隔离级别
- 读已提交:事务的每次读都创建一个Read View,
事务1中有多次读操作,如果事务2进行了修改,事务1多次读取的结果可能不同,可能出现不可重复读和幻读问题。 - 串行化:事务开始时创建一个Read View,该事务的每次读取均在一个Read View上进行,不存在脏读、不可重复读和幻读问题。