一般的DBMS系统,默认都会使用读提交(Read-Comitted,RC)作为默认隔离级别,如Oracle、SQL Server等,而MySQL却使用可重复读(Read-Repeatable,RR)。要知道,越高的隔离级别,能解决的数据一致性问题越多,理论上性能损耗更大,可并发性越低。隔离级别依次为
SERIALIZABLE > RR > RC > Read-Uncommited
在SQL标准中,前三种隔离级别分别解决了幻象读、不可重复读和脏读的问题。那么,为什么MySQL使用可重复读作为默认隔离级别呢?
1. 从Binlog说起
Binlog是MySQL的逻辑操作日志,广泛应用于复制和恢复。MySQL 5.1以前,Statement是Binlog的默认格式,即依次记录系统接受的SQL请求;5.1及以后,MySQL提供了Row和Mixed两个Binlog格式。
从MySQL 5.1开始,如果打开语句级Binlog,就不支持RC和Read-Uncommited隔离级别。要想使用RC隔离级别,必须使用Mixed或Row格式。
mysql> set tx_isolation='read-committed'; Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(1,1); ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT' |
那么,为什么RC隔离级别不支持语句级Binlog呢?我们关闭binlog,做以下测试。
会话1 |
会话2 |
use test; #初始化数据 create table t1(c1 int, c2 int) engine=innodb; create table t2(c1 int, c2 int) engine=innodb;
insert into t1 values(1,1), (2,2); insert into t2 values(1,1), (2,2);
#设置隔离级别 set tx_isolation='read-committed'; Query OK, 0 rows affected (0.00 sec)
#连续更新两次 mysql> Begin; Query OK, 0 rows affected (0.03 sec)
mysql> update t2 set c2 = 3 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0
mysql> update t2 set c2 = 4 where c1 in (select c1 from t1); Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t2; +------+------+ | c1 | c2 | +------+------+ | 1 | 4 | | 2 | 3 | +------+------+ 2 rows in set (0.00 sec)
mysql> commit; |
#设置隔离级别 set tx_isolation='read-committed'; Query OK, 0 rows affected (0.00 sec)
#两次更新之间执行删除 mysql> delete from t1 where c1 = 2; Query OK, 1 row affected (0.03 sec)
|
由以上测试知,RC隔离级别下,会话2执行时序在会话1事务的语句之间,并且会话2的操作影响了会话1的结果,这会对Binlog结果造成影响。
由于Binlog中语句的顺序以commit为序,如果语句级Binlog允许,两会话的执行时序是
#会话2 set tx_isolation='read-committed'; delete from t1 where c1 = 2; commit;
#会话1 set tx_isolation='read-committed';
Begin;
update t2 set c2 = 3 where c1 in (select c1 from t1);
update t2 set c2 = 4 where c1 in (select c1 from t1);
select * from t2; +------+------+ | c1 | c2 | +------+------+ | 1 | 4 | | 2 | 2 | +------+------+ 2 rows in set (0.00 sec)
commit; |
由上可知,在MySQL 5.1及以上的RC隔离级别下,语句级Binlog在DR上执行的结果是不正确的!
那么,MySQL 5.0呢?5.0允许RC下语句级Binlog,是不是说很容易产生DB/DR不一致呢?
事实上,在5.0重复上述一个测试,并不存在这个问题,原因是5.0的RC与5.1的RR使用类似的并发和上锁机制,也就是说,MySQL 5.0的RC与5.1及以上的RC可能存在兼容性问题。
下面看看RR是怎么解决这个问题的。
2. 默认隔离级别-可重复读
导致RC隔离级别DB/DR不一致的原因是:RC不可重复读,而Binlog要求SQL串行化!
在RR下,重复以上测试
会话1 |
会话2 |
use test; #初始化数据 create table t1(c1 int, c2 int) engine=innodb; create table t2(c1 int, c2 int) engine=innodb;
insert into t1 values(1,1), (2,2); insert into t2 values(1,1), (2,2);
#设置隔离级别 set tx_isolation='repeatable-read'; Query OK, 0 rows affected (0.00 sec)
#连续更新两次 mysql> Begin; Query OK, 0 rows affected (0.03 sec)
mysql> update t2 set c2 = 3 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0
mysql> update t2 set c2 = 4 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from t2; +------+------+ | c1 | c2 | +------+------+ | 1 | 4 | | 2 | 4 | +------+------+ 2 rows in set (0.00 sec)
mysql> commit; |
#设置隔离级别 set tx_isolation=' repeatable-read'; Query OK, 0 rows affected (0.00 sec)
#两次更新之间执行删除 mysql> delete from t1 where c1 = 2; --阻塞,直到会话1提交
Query OK, 1 row affected (18.94 sec)
|
与RC隔离级别不同的是,在RR中,由于保证可重复读,会话2的delete语句会被会话1阻塞,直到会话1提交。
在RR中,会话1语句update t2 set c2 = 3 where c1 in (select c1 from t1)会先在t1的记录上S锁(5.1的RC中不会上这个锁,但5.0的RC会),接着在t2的满足条件的记录上X锁。由于会话1没提交,会话2的delete语句需要等待会话1的S锁释放,于是阻塞。
因此,在RR中,以上测试会话1、会话2的依次执行,与Binlog的顺序一致,从而保证DB/DR一致。
幻象读
除了保证可重复读,MySQL的RR还一定程度上避免了幻象读(幻象读是由于插入导致的新记录)。(为什么说一定程度呢?参考第3节可重复读和串行化的区别。)
会话1 |
会话2 |
use test; #初始化数据 create table t1(c1 int primary key, c2 int) engine=innodb; create table t2(c1 int primary key, c2 int) engine=innodb;
insert into t1 values(1,1), (10,10); insert into t2 values(1,1), (5,5), (10,10);
#设置隔离级别 set tx_isolation='repeatable-read'; Query OK, 0 rows affected (0.00 sec)
#连续更新两次 mysql> Begin; Query OK, 0 rows affected (0.03 sec)
mysql> update t2 set c2 = 20 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0
mysql> delete from where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from t2; +------+------+ | c1 | c2 | +------+------+ | 5 | 5 | +------+------+ 2 rows in set (0.00 sec)
mysql> commit; |
#设置隔离级别 set tx_isolation=' repeatable-read'; Query OK, 0 rows affected (0.00 sec)
#两次更新之间执行插入 mysql> insert into t1 values(5,5); --阻塞,直到会话1提交
Query OK, 1 row affected (18.94 sec)
|
由上述例子知,会话2的插入操作被阻塞了,原因是RR隔离级别中,除了记录锁外,还会上间隙锁(gap锁)。例如,对于表t1,update t2 set c2 = 20 where c1 in (select c1 from t1)以上的锁包括:
(-∞, 1), 1, (1, 10), 10, (10, +∞)
由于对t1做全表扫描,因此,所有记录和间隙都要上锁,其中(x,y)表示间隙锁,数字表示记录锁,全部都是S锁。会话2的insert操作插入5,位于间隙(1,10),需要获得这个间隙的X锁,因此两操作互斥,会话2阻塞。
SQL标准的RR并不要求避免幻象读,而InnoDB通过gap锁来避免幻象,从而实现SQL的可串行化,保证Binlog的一致性。
要想取消gap lock,可使用参数innodb_lock_unsafe_for_binlog=1,默认为0。
3. 可重复读与串行化的区别
InnoDB的RR可以避免不可重复读和幻象读,那么与串行化有什么区别呢?
会话1 |
会话2 |
use test; #初始化数据 create table t3(c1 int primary key, c2 int) engine=innodb;
#设置隔离级别 set tx_isolation='repeatable-read'; Query OK, 0 rows affected (0.00 sec)
mysql> Begin; Query OK, 0 rows affected (0.03 sec)
mysql> select * from t3 where c1 = 1; Empty set (0.00 sec)
mysql> select * from t3 where c1 = 1; Empty set (0.00 sec)
mysql> update t3 set c2 =2 where c1 = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t3 where c1 = 1; +----+------+ | c1 | c2 | +----+------+ | 1 | 2 | +----+------+ 1 row in set (0.00 sec)
mysql> commit; |
#设置隔离级别 set tx_isolation=' repeatable-read'; Query OK, 0 rows affected (0.00 sec)
mysql> insert into t3 values(1,1); Query OK, 1 row affected (0.05 sec)
|
由上述会话1中,连续两次读不到数据,但更新却成功,并且更新后的相同读操作就能读到数据了,这算不算幻读呢?
其实,RR隔离级别的防止幻象主要是针对写操作的,即只保证写操作的可串行化,因为只有写操作影响Binlog;而读操作是通过MVCC来保证一致性读(无幻象)。
然而,可串行化隔离级别要求读写可串行化。使用可串行化重做以上测试。
会话1 |
会话2 |
use test; #初始化数据 create table t3(c1 int primary key, c2 int) engine=innodb;
#设置隔离级别 set tx_isolation='SERIALIZABLE'; Query OK, 0 rows affected (0.00 sec)
mysql> Begin; Query OK, 0 rows affected (0.03 sec)
mysql> select * from t3 where c1 = 1; Empty set (0.00 sec)
mysql> select * from t3 where c1 = 1; Empty set (0.00 sec)
mysql> update t3 set c2 =2 where c1 = 1; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0
mysql> select * from t3 where c1 = 1; Empty set (0.00 sec)
mysql> commit; |
#设置隔离级别 set tx_isolation='SERIALIZABLE'; Query OK, 0 rows affected (0.00 sec)
mysql> insert into t3 values(1,1); #阻塞,直到会话1提交
Query OK, 1 row affected (48.90 sec) |
设置为串行化后,会话2的插入操作被阻塞。由于在串行化下,查询操作不在使用MVCC来保证一致读,而是使用S锁来阻塞其他写操作。因此做到读写可串行化,然而换来就是并发性能的大大降低。
4. 小结
MySQL使用可重复读来作为默认隔离级别的主要原因是语句级的Binlog。RR能提供SQL语句的写可串行化,保证了绝大部分情况(不安全语句除外)的DB/DR一致。
另外,通过这个测试发现MySQL 5.0与5.1在RC下表现是不一样的,可能存在兼容性问题。
尊重版权,原链接:http://www.cnblogs.com/vinchen/archive/2012/11/19/2777919.html
相关推荐
MySQL默认的事务隔离级别是可重复读,它能防止脏读和不可重复读。在这个级别下,事务在整个事务期间可以看到一致的数据视图,即同一查询始终返回相同的结果,除非事务自己对数据进行了修改。然而,幻读问题依然存在...
3. 在可重复读隔离级别中,事务多次读取同一数据时,即使其他事务已经提交了对数据的修改,读取的结果也保持一致,解决了不可重复读问题。但是,仍然可能发生幻读问题。 4. 在串行化隔离级别中,事务是完全串行化的...
可重复读是 MySQL InnoDB 存储引擎的默认隔离级别。在可重复读级别下,一个事务在整个事务期间可以看到的是它开始时的数据视图,即使其他事务在此期间对数据进行了修改并提交。这意味着在一个事务内多次执行相同的...
- `Isolation.DEFAULT`:使用数据库自身的默认隔离级别,对于 MySQL 就是 REPEATABLE READ。 - `Isolation.READ_UNCOMMITTED`:允许脏读、不可重复读和幻读。 - `Isolation.READ_COMMITTED`:防止脏读,但允许不可...
MySQL提供了下面4种隔离级:序列化(SERIALIZABLE)、可重复读(REPEATABLE READ)、提交读(READ COMMITTED)、未提交读(READ UNCOMMITTED)。 事务隔离级别 语法格式: SET [GLOBAL | SESSION] TRANSACTION ...
Repeatable Read(可重读)是 MySQL 的默认隔离级别,解决了不可重复读的问题,允许多次读取同一数据集时保持一致性。但在这一级别下,事务仍可能遇到幻读问题,即在事务内多次查询,第一次查询时不存在的记录在后续...
3. **可重复读(REPEATABLE-READ)**:这是MySQL InnoDB的默认隔离级别。在可重复读中,事务可以在其开始时获取一致性视图,这意味着在整个事务期间,多次读取相同的数据块将始终返回相同的结果,除非事务本身对其进行...
- **可重复读(Repeatable Read)**:MySQL的默认隔离级别,它保证在同一事务内多次读取相同数据的结果一致,防止了脏读和不可重复读,但在某些情况下仍可能出现幻读。 - **串行化(Serializable)**:最高的隔离...
Mysql默认的事务隔离级别是可重复读,可以通过设置tx_isolation变量来修改事务隔离级别。 锁机制 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,锁机制是用来解决数据并发访问的一致性和有效性...
在多用户同时访问数据库的情况下,事务隔离级别能够确保数据的一致性和完整性,避免并发操作带来的各种问题,如脏读、不可重复读和幻读。 1. 脏读(Dirty Read): 脏读指的是在一个事务中,事务A读取了事务B还未...
**隔离级别**定义了事务并发执行时的隔离程度,即控制一个事务对其他事务的影响,防止脏读、不可重复读和幻读等问题。MySQL支持四种不同的隔离级别: 1. **READ UNCOMMITTED (未提交读)**:最低的隔离级别,允许...
然而,对于幻读,MySQL的InnoDB存储引擎在可重复读隔离级别下通过使用Next-Key Locks(一种扩展的行级锁)来防止幻读,这通常意味着在同一个事务内,即使其他事务插入了新的记录,当前事务也无法看到这些新插入的...
- 这是ORACLE的默认隔离级别。 - 每条SQL语句读取的数据是一致的,不会读取到未提交的数据。 - 但是可能会遇到非重复读和幻像读的问题。 2. **Serializable**: - 保证事务内部的读取结果具有一致性,不会出现...
Read Uncommitted是最低的隔离级别,在这个级别下,一个事务可以读取其他事务未提交的数据,这可能会导致脏读、不可重复读和幻读的问题。 Read Committed是第二个级别,在这个级别下,一个事务只能读取其他事务已经...
MySQL的默认隔离级别是可重复读,它使用多版本并发控制(MVCC)来实现,这样在读操作中可以避免加锁,提高并发性能,而写操作会更新数据版本,保证一致性。 理解并选择合适的事务隔离级别对于数据库的设计和优化至...
MySQL数据库中的可重复读(Repeatable Read)和幻读(Phantom Read)是事务隔离级别的两个关键概念,尤其是在使用InnoDB存储引擎时。事务隔离级别是数据库为了保证并发操作的正确性和一致性而设定的规则,它包括四种...
MySQL的InnoDB存储引擎默认使用可重复读隔离级别,通过多版本并发控制(MVCC)来实现,以减少锁的竞争,提高并发性能。 然后是锁机制,它是实现不同隔离级别的关键手段: 1. **共享锁(S锁/读锁)**:允许一个事务...
脏读、不可重复读和幻读是在事务隔离级别不足的情况下可能出现的数据一致性问题。通过合理设置事务的隔离级别以及利用数据库提供的锁定机制,可以在一定程度上避免这些问题的发生。在Java应用程序中,可以利用Spring...
- ` isolation`: 事务隔离级别,可以设置为DEFAULT(使用数据库默认隔离级别)、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ或SERIALIZABLE。 - `readOnly`: 是否只读事务,如果是true,那么事务不会进行...