事务并发遇到的问题
- 脏写(写-写)
一个事务修改了另一个未提交事务修改过的数据 - 脏读(写-读)
一个事务读到了另一个未提交事务修改过的数据 - 不可重复度(读-写-读)
一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那就意味着发生了不可重复读 - 幻读(读-写-读)
一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了幻读
有哪些事务隔离级别
我们上边介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题也有轻重缓急之分,我们给这些问题按照严重性来排一下序:
脏写 > 脏读 > 不可重复读 > 幻读
为了解决上面四种并发事务产生的问题,设计了四种事务隔离级别。
- 读未提交:可能发生脏读、不可重复读和幻读问题。
- 读已提交:可能发生不可重复读和幻读问题,但是不可以发生脏读问题。
- 可重复读:可能发生幻读问题,但是不可以发生脏读和不可重复读的问题。
- 可串行化:各种问题都不可能发生。
注:脏写太严重了,单独控制,任何情况写都不允许出现脏写
默认事务隔离级别
mysql的默认隔离级别是repeatable,可重复度
oracle,sqlserver的默认隔离级别是read commited, 读已提交
why
为什么mysql的默认事务隔离级别是可重复读而不是读已提交呢?这个是有历史原因的,当然要从我们的主从复制开始讲起了!
主从复制,是基于什么复制的?是基于binlog复制的
binlog有几种格式?三种,分别是:
- statement:记录的是修改SQL语句
- row:记录的是每行实际数据的变更
- mixed:statement和row模式的混合
那Mysql在5.0这个版本以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!
接下来,就要说说当binlog为STATEMENT格式,且隔离级别为读已提交(Read Commited)时,有什么bug呢?如下图所示,在主(master)上执行如下事务
此时,在master上执行查询语句,可用查到b=3这条记录,因为在master山是先执行删除后执行插入;而在slave上执行查询,结果为空,因为插入语句先提交,先执行插入后执行删除。
如何解决这个问题?两种方案: - 方案一:隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住
- 方案二:将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!
问题:既然5.1转换支持row格式的binlog日志,为什么5.1之后不把默认事务隔离级别改操读已提交呢?
为什么oracle,sqlserver默认用读已提交?读已提交与可重复读有什么区别?
首先,一般是不用读未提交的,事务隔离性太差,几乎不用。
可串行化太严格,每次读操作都会枷锁,严重影响效率,不符合高并发场景
所以主要讨论为什么用读已提交而不用可重复读。
假设有一张表
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`color` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
数据如下:
+----+-------+
| id | color |
+----+-------+
| 1 | red |
| 2 | white |
| 5 | red |
| 7 | white |
+----+-------+
- 原因一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
此时执行语句select * from test where id <3 for update;在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据! 而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!
ps:在RC隔离级别下并不是不会出现死锁,只是出现几率比RR低而已! - 原因二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
此时执行语句
update test set color = 'blue' where color = 'red';在RC隔离级别下,其先走聚簇索引,进行全部扫描。加锁如下
但在实际中,MySQL做了优化,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录放锁。实际加锁如下
然而,在RR隔离级别下,走聚簇索引,进行全部扫描,最后会将整个表锁上,如下所示
在RC级别下,不可重复读问题怎么办?需要解决么?
不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle的默认隔离级别就是RC