并发产生的问题:
脏写 | |
脏读 | |
不可重复读 Nonrepeatable Read | |
幻读 phantom Read | 用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的'幻影'行 |
脏读eg
时间 | 会话A | 会话B |
1 | Select * from test | |
2 | 1 row A:1 | |
3 | Insert into t select 2 | |
4 | Select * from test | |
5 | 2 row a:1 a:2 |
时间 | 会话A | 会话B |
1 | Begin | Begin |
2 | 查询账户余额1000 | |
3 | 取出500,余额500 | |
4 | 查询账户余额500(脏读) | |
5 | 撤销事务,余额1000 | |
6 | 转入100,余额600 | |
7 | 提交事务 |
不可重复读
时间 | 会话A | 会话B |
1 | Begin | Begin |
2 | Select * from test | |
3 | 1 row A:1 | |
4 | Insert into t select 2 | |
5 | Commit | |
6 | Select * from test | |
7 | 2 row a:1 a:2 |
幻读
时间 | 会话A | 会话B |
1 | 开始事务 | |
2 | 开始事务 | |
3 | 统计总存款10000元 | |
4 | 新增一个存款账户,存款100元 | |
5 | 提交事务 | |
6 | 再次统计总存款数10100元(幻读) |
事务隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
读未提交 read uncommited | 是 | 是 | 是 | 否 | 是 |
读已提交(RC) read committed | 否 | 是 | 是 | 否 | 是 |
可重复读(RR) repeatable read | 否 | 否 | 是 | 否 | 否 |
串行化-serializable | 否 | 否 | 否 | 否 | 否 |
读未提交 read uncommited | 所有事务都可以看到其他未提交事务的执行结果 | 很少用于实际应用 性能不比其他级别好多少 读取未提交数据-脏读-Dirty Read |
读已提交(RC) read committed | 一个事务提交之后,才会被其他事务看到 解决脏读,不可重复读 大多数数据库系统默认隔离级别 | 满足隔离简单定义 一个事务只能看见已经提高事务所做的改变 同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果 一个事务可以读取到另一个尚未提交事务的修改数据 |
可重复读(RR) repeatable read | MySQL的默认事务隔离级别 确保同一事务多个实例在并发读取数据时,看到同样的数据行。 解决脏写、脏读、不可重复读 | 基于undo log多版本链条 + ReadView机制 解决多个事务读写的时候避免频繁加互斥锁的影响 |
串行化-serializable | 最高的隔离级别 通过强制事务排序,使之不可能相互冲突,解决幻读问题 | 每个读数据行上加上共享锁 可能导致大量的超时现象和锁竞争 |
查看事务隔离级别
show variables like 'tx_isolation';
复制
设置隔离级别
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
复制
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
复制
隔离性底层实现
锁+MVCC
在使用 READ COMMITTD、REPEATABLE READ 这两种隔离级别的事务下,每条记录在更新的时候都会同时记录一条回滚操作,就会形成一个版本链,在执行普通的SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复这个ReadView就好了。
READ COMMITTD隔离级别下导致不可重复读的根本原因
READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView
REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView
多版本并发控制(MVCC),是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务戳关联,读操作只读事务开始前的数据库的快照。这样在读操作的时候不会阻塞写操作,写操作不会阻塞读操作的同时,也避免了脏读和不可重复读。
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并发性能的一种机制。
关键词:事务版本号、表的隐藏列(事务版本号,undo log链)、ReadView
当前读
快照读
实现原理
MVCC就是多版本并发控制,用来解决读写冲突。准确的说,MVCC主要基于"维护一条数据的多个版本,进而保证在读操作的同时不会阻塞写操作,写操作的同时也不会阻塞读操作"。在mysql中的具体实现主要是由隐藏字段,undolog,readview等去完成的,具体实现逻辑看下面的MVCC实现原理。
roll_pointer:回滚指针,指向这条记录的上一个版本
trx_id:事务ID,记录创建/修改这条记录的事务ID,用于版本比较,从而找到快照
ReadView
RR隔离级别
当事务第一次执行查询sql时会生成一致性视图ReadView,它由执行查询时所有未提交事务ID数组[未提交事务min_trx_id,未提交事务max_trx_id]组成,查询的数据结果需要跟ReadView做比较从而得到快照结果。
版本链比对规则
如果trx_id < min_id,表示这个版本是已提交的事物生成的,这个数据是可见的
如果trx_id > max,表示这个版本是由将来启动的事务生成的,是肯定不可见的
如果min_id <= trx_id<= max_id
若row的trx_id在数组中
表示这个版本是由还没提交的事务生成的,不可见,当前自己的事务是可见的
若row的trx_id不在数组中
表示这个版本是已经提交的事务生成的,是可见的
QA
MVCC是否解决了幻读
幻读是由MCCC多版本并发控制机制实现
InnoB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control 间隙锁)机制解决幻读问题
多版本只是解决不可重复读问题,而加上间隙锁(并发控制)解决幻读问题
同一事务中,同一查询多次进行时候,由于其他插入操作的事务提交,导致每次返回的结果集