mysql事务隔离与原理

Posted by hcy on October 21, 2020

mysql事务隔离与原理

事务遇到的问题

数据库的隔离级别会遇到下面三个问题,分别是脏读,不可重复读,幻读。

脏读

​ 事务A读取到事务B尚未提交的数据,这是不能接受的,如果事务B后面回滚了,事务A就相当于读取到了不存在的脏数据。

不可重复读

​ 指事务A读取一条数据 “name=张三”,然后事务B修改了该条数据“update name=李四”并提交,事务A再次读取时发现name被修改了。这在有些情况下是能接受的,有些情况下是不能接收的,要根据业务需求来。

幻读

​ 与不可重复读类似,A先读取“where id > 10”查询出2条数据,此时B插入一条数据并提交,然后A再次读取时发现读取到了三条数据,读取到了幻行,和不可重复读类似,只是关注的点不一样,一个关注数据内容的变化,一个关注数据数量的变化。

事务隔离级别

​ 上面描述的问题在有的业务场景下不允许发生,但在有的业务场景下确是允许发生的,所以mysql提供了四种事务隔离级别,能够由程序员自己根据业务需求配置mysql,选择哪种隔离级别。

四种隔离级别分别是,读未提交,读已提交,可重复读,序列化。

读未提交级别Read Uncommitted

​ 允许读取其他事务未提交的数据,也就是允许上面那三种现象的发生。

读已提交Read Committed

​ 允许读取已经提交的数据,未提交的不能读取,这能解决脏读问题,但是无法解决不可重复读和幻读的问题。

可重复读Repeatable Read

​ 不允许读取到其他事务已提交的数据,第一次读取时建立当前数据的快照,下次读取仍读取该快照,这样读取到的内容就不会受其他事务插入或修改的数据影响了。

串行化Serializable

​ 事务的提交串行化,可以解决上面所有的问题。

mvcc快照原理

​ 每行数据保存两个隐藏的列,分别是创建时的版本号和删除时的版本号。

进行查询时只查询创建版本号小于等于当前事务,且删除版本号为空或大于当前事务的数据。

进行更新时将数据的删除版本号设置为当前值,并复制一条数据进行更新,将创建版本号设为当前事务的

删除时将删除版本号设置当前事务

行锁和间隙锁

​ 如果当前数据库数据如下(id添加主键索引,number添加普通索引):

id number
1 1
2 2
3 3
4 4

如果事务A执行下面语句,就会对id = 2这一条语句加行锁,其他事务无法修改或删除该条数据。

select * from table where id = 2 for update

如果事务A执行下面的语句,将会对 id>2 的部分添加行锁,这里分别是id=3和id=4,除此之外还会对(4 - max)添加间隙锁,为什么是间隙锁而不是行锁和,因为此时(4-max)的数据还不存在,也就不能添加行锁了,只能使用间隙锁,锁住暂时不存在的数据的索引间隙。

select * from table where id > 2 for update

上面只是间隙锁对唯一索引上的规则,如果是普通索引会有不一样的行为。

select * from table where number = 2 for update

执行这条语句,他与上面不同的是他是查询的number字段而不是id,此时它不但会锁定number = 2这条数据,还会添加间隙锁,锁住number处于 (1-2)(2-3)之间的数据。

那么如果number<=1 或者 number >=3就能正常修改吗,当然不是的。

如果number <=1 则需要id在区间(-max , 1]   (3 , max)
如果number>=3 则需要id在区间 [3 , max)   (-max , 2)

转载请注明出处:https://www.huangchaoyu.com/2020/10/21/mysql事务隔离与原理/