本文转载自微信公众号《JAVA日之录》,作者单音。转载本文请联系JAVA日知录公众号。小张:面试官你好。我是来面试的采访者:你好,小张。看了你的简历,精通MySQL数据库。那你一定要知道交易,能不能说说交易的特点?小张:一笔交易有4个特征,即ACID。原子性:事务开始后的所有操作,要么全部成功,要么全部失败。一致性:事务开始前后不违反数据库的完整性约束。比如A给B转钱,不可能A扣钱B收不到钱。隔离:当多个事务并发访问时,事务是隔离的。持久性:事务完成后,事务对数据库的操作保存在数据库中,不可回滚。面试官:嗯,没错。然后告诉我事务有哪些隔离级别?小张:事务隔离级别从高到低有四种隔离级别,分别是:序列化(SERIALIZABLE)、可重复读(REPEATABLEREAD)、读提交(READCOMMITTED)、读未提交(READUNCOMMITTED)。面试官:嗯,你能告诉我这四个隔离级别会造成什么问题吗?公众号)张:好的,面试官。如果数据库使用READUNCOMMITTED隔离级别,会造成脏读。其他人可以在事务提交之前看到它,所以你不能保证你读取的数据是最终数据。如果别人回滚事务,就会出现脏数据问题。READCOMMITTED是指一个事务只能读取其他事务已经提交的数据,这样不会有脏读的问题,但是会造成“不可重复读”的问题。比如事务A把一个人的名字从张三改成了李四,事务B在事务A提交前读取了张三,但是事务A提交后变成了李四。可重复读(REPEATABLEREAD):可重复读是为了解决READCOMMITTED带来的不可重复读问题,即事务不会读取其他事务对已有数据的修改,即使数据已经提交。也就是说,一开始事务读取的是什么,在事务提交之前的任何时候,数据都是一样的。虽然解决了不可重复读的问题,但是会带来幻读的问题。比如事务A把张三改成了李四,事务B插入了一个叫李四的用户。这时事务A搜索一个叫李四的用户,发现还有一个,李四有2个。这就是幻读。序列化(SERIALIZABLE):它解决了上面所有的问题,但是效率最低,它把事务的执行变成了顺序执行。面试官:回答得好,你知道MySQL默认的隔离级别是多少吗?小张:Mysql默认的隔离级别是REPEATABLEREAD,而Oracle使用的是READCOMMITTED。面试官:但是我们用MySQL的时候,没有出现幻读。如何解决?小张擦了擦汗,开始有些紧张了:嗯,InnoDB主要是用锁来解决幻读的问题。面试官:是的,用到了锁,那么它是如何实现的呢?小张:我……我突然有点事,先回去了。面试官:要了解InnoDB是如何解决幻读的,首先要知道InnoDB有什么样的锁。RecordLock:对单行记录加锁GapLock:GapLock,锁定一个范围,而不是记录本身,遵循左开右闭的原则Next-KeyLock:结合GapLock和RecordLock,锁定一个范围,并锁定记录本身。主要解决REPEATABLEREAD隔离级别下的幻读问题。注意,如果使用唯一索引,Next-KeyLock会降级为RecordLock,即只锁定索引本身,不锁定范围。也就是说,Next-KeyLock的前提是事务隔离级别为RR且查询索引使用的非唯一索引和主键索引。下面用具体的例子来模拟上面的幻读问题:CREATETABLET(idint,namevarchar(50),f_idint,PRIMARYKEY(id),KEY(f_id))ENGINE=InnoDBDEFAULTCHARSET=utf8insertintoTSELECT1,'张三',10;insertintoTSELECT2,'李斯,30岁;InnoDB为数据库中的索引维护了一组B+树,用于快速定位行记录。B+索引树是有序的,所以这张表的索引会被分成几个区间。事务A执行如下语句,需要将张三换成李四。select*fromt;updatesetname='李四'wheref_id=10;此时SQL语句使用的是非唯一索引,所以使用Next-KeyLock加锁不仅会在f_10=10这一行加行锁,还会在该行记录的两边加间隙锁,即(-∞,10],(10,30]加了间隙锁,此时事务B要执行下面的语句,会报错[Err]1205-Lockwaittimeoutexceeded;尝试重启事务INSERTINTOTSELECT3,'WangWu',10;--满足行锁,执行阻塞INSERTINTOTSELECT4,'赵刘',8;--满足间隙锁,执行阻塞INSERTINTOTSELECT5,'SunQi',18;--满足间隙锁,执行阻塞不仅插入f_id=10的记录还需要等待事务A提交,f_id<10、10
