MySQL [pom_5]> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
如果事务隔离级别显示REPEATABLE-READ,即是可重复读。
事务的四种隔离级别
(引自: Innodb中的事务隔离级别和锁的关系)
在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
可重复读和幻读
在可重复读中,该sql第一次读取到数据后,就将这些数据加锁(悲观锁),其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来实现。
实际测试结果
对相同db实例创建两个连接:a和b,来测试select, select for update, update语句的生效情况
不使用select for update行级锁
a先提交 | b先提交 | |
---|---|---|
a先开启事务 | b的update内容 | a的update内容 |
a先开启事务 | b的update内容 | a的update内容 |
结果是谁最后提交,谁的结果生效
a先使用select for update, update行级锁
a使用select for update | a使用select for update | a使用update | |
---|---|---|---|
a开启事务,b不开启 | b里面的select for update和update被阻塞 | b不受影响 | b里面的select for update和update被阻塞 |
a不开启事务,b开启 | b不受影响 | b不受影响 | b不受影响 |
a,b开启事务 | b里面的select for update和update被阻塞 | b不受影响 | b里面的select for update和update被阻塞 |
a,b不开启事务 | b不受影响 | b不受影响 | b不受影响 |
结果是只有使用begin显式开启事务时,使用select for update才会对数据加上行级锁,对其他连接的的select for update(对select没有影响)和update造成阻塞效果。
a上锁后修改提交,b读取到的内容
下面是a开启事务,然后使用a使用select for update,接着b使用select for update/select,a执行updat,最后commit,观察整个过程中b的查询效果。
b使用select for update | b使用select | |
---|---|---|
b开启事务 | b里面的select for update被阻塞,a提交后,b读到的是a修改的内容 | b不受a阻塞,读到的仍是b事务开启时的内容 |
b不开启事务 | b里面的select for update被阻塞,a提交后,b读到的是a修改的内容 | b不受a阻塞,读到的数据在a提交后变化 |
在连接a使用begin开启事务之后,select for update和update对连接b的阻塞效果
a先执行select for update | a先执行update | |
---|---|---|
b后执行select | 未阻塞 | 未阻塞 |
b后执行select for update | 阻塞 | 阻塞 |
b后执行update | 阻塞 | 阻塞 |