事务隔离级别
查看数据库的隔离级别命令: select @@tx_isolation;
事务并发执行遇到的问题
脏写(Dirty Write)
如果一个事务修改了另一个未提交事务修改过的数据,那就意味着发生了脏写脏读(Dirty Read)
如果一个事务读到了另一个未提交事务修改过的数据,那就意味着发生了脏读不可重复读
如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那就意味着发生了不可重复读
事务中前后两次读取到的值不一样( 在Session B中提交了几个隐式事务(注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了number列为1的记录的列name的值,每次事务提交之后,如果SessionA中的事务都可以查看到最新的值,这种现象也被称之为不可重复读)幻读
如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了幻读
幻读强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录
问题的严重性:
脏写 > 脏读 > 不可重复读 > 幻读
SQL标准中的四种隔离级别
- READ UNCOMMITTED:未提交读
- READ COMMITTED:已提交读
- REPEATABLE READ:可重复读
- SERIALIZABLE:可串行化
SQL标准
中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED |
Possible | Possible | Possible |
READ COMMITTED |
Not Possible | Possible | Possible |
REPEATABLE READ |
Not Possible | Not Possible | Possible |
SERIALIZABLE |
Not Possible | Not Possible | Not Possible |
READ UNCOMMITTED
隔离级别下,可能发生脏读
、不可重复读
和幻读
问题。READ COMMITTED
隔离级别下,可能发生不可重复读
和幻读
问题,但是不可以发生脏读
问题。REPEATABLE READ
隔离级别下,可能发生幻读
问题,但是不可以发生脏读
和不可重复读
的问题。SERIALIZABLE
隔离级别下,各种问题都不可以发生。
脏写的问题太严重了 ,所以 无论哪种隔离级别都不允许脏写的情况发生
如何设置事务的隔离级别
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
查看隔离级别:
SHOW VARIABLES LIKE ‘transaction_isolation’;
or SELECT @@transaction_isolation;
MVCC原理
版本链
Innodb聚簇索引记录中都包含两个必要的隐藏列: trx_id, roll_pointer (row_id非必要,当创建的表中有主键或者非NULL的UNIQUE键时都不会包含row_id列)
roll_pointer 指向一条undolog
insert undolog 只在事务回滚时起作用,当事务提交后 ,该类型的undo日志就没有用了
innodb使用锁来保证不会有脏写情况的发生
每次对记录进行改动,都会记录一条undo日志,每条undo日志都会有一个roll_pointer属性,可以将这些undo日志都连起来,串成一个链表。这个链表我们称为 版本链
版本链的头节点就是当前记录的最新的值。另外,每个版本中还包含生成该版本时对应的事务id
ReadView
ReadUncommited隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本。
对于使用serializable隔离级别的事务,规定了使用加锁的方式来访问记录
对于使用READ COMMITTED 和 REPEATABLE READ隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录,也就是说不能无脑读取最新版本的记录,需要判断版本链中哪个版本对当前事务是可见的。
为此,提出了ReadView的概念,包含4个比较重要的内容:
- m_ids: 表示在生成ReadView是当前系统中活跃的读写事务的id列表
- min_trx_id: 在生成ReadView时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
- max_trx_id: 在生成ReadView时,系统应该分配给下一个事务的id值
- creator_trx_id: 表示生成该ReadView的事务id
有了ReadView,就可以判断记录的某个版本是否可见:
- trx_id 等于 creator_trx_id ,可见
- trx_id < min_trx_id ,可见
- trx_id >= max_trx_id ,不可见
- trx_min_id < trx < trx_max_id, 如果trx_id在m_ids中,不可见,否则可见。
如果当前ReadView不可见,就顺着版本链往下查找,直到最后一个版本。
READ COMMITTED 和 REPEATABLE READ的主要区别就是生成ReadView的时机不同:
READ COMMITED — 每次读取数据前都生成一个ReadView
REPEATABLE READ — 在第一次读取数据时生成一个ReadView
MVCC小结
所谓MVCC,指的就是使用READ COMMITIED、REPEATABLE READ这两种隔离级别的事务在执行普通的select操作时,访问记录版本链的过程。这样可以使不同事务的读-写,写-读操作并发执行,从而提升系统性能。
READ COMMITTED、REPEATABLE READ 这两个隔离级别很大的不同就是生成ReadView的时机不同,RC是每次select操作前都会生成一个ReadView,RR只在第一次进行普通select操作前生成一个ReadView,之后的查询操作都重复使用和这个ReadView
ps: 关于purge(清除)
- insert undo在事务提交后就被释放了,而update undo由于还需要支持MVCC,不能立即删除
- 为了支持mvcc ,delete mark操作仅仅是在记录上打一个删除标记,并没有真正删除。等后台的purge线程去删除
ACID的底层实现原理
mysql基础架构:
连接线程处理( 连接处理,授权认证,安全)
查询缓存、分析器、优化器(查询解析,分析,优化,缓存,所有内建函数(日期、时间、数字、加密函数等)
存储引擎(存储和提取数据. myisam、innodb)
ACID特性:
原子性 Atomicity
一致性 Consistency
隔离性 Isolation
持久性 Durability
原子性:
基于undo log : sql执行的相关信息记录在操作日志里,回滚执行相反的操作
持久性:
redo log
mysql数据存在磁盘中,io效率低。 innodb提供了一个缓存buffer,包含了部分磁盘数据页的一个映射,作为访问数据库的一个缓冲
写数据也会先向buffer写数据,然后再同步到磁盘上。提高效率但是也有数据丢失的风险。引入redo log解决这个问题
redo log预写式日志,所有的日志会先写到日志里面再同步到磁盘。如果mysql宕机了 还有redolog可以恢复数据
redolog为什么比buffer同步数据快:
buffer随机写的io,每次修改的数据位置随机。buffer持久化数据是以数据页page为单位,mysql默认的page数据页大小为16k,很多无效io
redo log文件尾部追加,顺序io。
redo log持久化机制:
参数innodb_flush_log_at_trx_commit
0: 等待主线程每秒shuax
1:事务提交同步写入磁盘
2:事务提交后异步写入磁盘
隔离性
写-写:锁 , 行锁、表锁、间隙锁
写-读:MVCC(脏读、不可重复读、幻读)
一致性
上面的三个特性保证了数据的一致性,在操作前或者操作后数据都是有效且正确的
什么是MVCC
Multi-Version Concurrency Control 即:多版本并发控制,为了提高数据库的并发性能
当前读select
- select lock in share mode (共享锁)
- select for update (排它锁)
- update (排它锁)
- insert (排它锁)
- delete (排他锁)
- 串行化事务隔离级别
快照读
快照读的实现是据图MVCC实现的,因为是多版本,所以快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据
- 不加锁的select操作
数据库四种隔离级别:
- 读未提交
- RC 读已提交(常用)
- RR 可重复读(常用,mysql默认的隔离级别,RR默认是开启了间隙锁的)
- 串行化
MVCC undolog+版本链+readview(快照)
读已提交通过版本链和ReadView实现:
readView的数据结构:
m_ids: 在生成ReadView时当前系统中活跃的镀锡数据的 事务id 列表
min_trx_id: 在生成readView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
max_trx_id: 生成ReadView时,系统应该分配给下一个事务的id值
creator_trx_id: 生成该ReadVIew的事务的事务id
ReadView怎么判断版本链中的哪个版本可用?
- trx_id == creator_id: 可以访问这个版本(这条记录的这个事务是自己本身创建)
- trx_id < min_trx_id : 可以访问,都是已经commit了的事务
- trx_id > max_trx_id : 不可以访问
- min_trx_id <= trx_id <= max_trx_id : 不可以访问,都是未commit的事务,反之可以
读已提交和可重复读生成readView的时机是不同的:
RC是每次执行select查询的时候,就会生成一个ReadView,以每一个select查询为单位。 所以会导致在同一个事务里面两个select会查询出不同的结果
RR可重复读生成ReadView是以一个事务为单位,即同一个事务里面多次select都使用同一个ReadView。
RR虽然解决了不可重复读的问题,但是会存在幻读的问题。幻读在innodb这个引擎里面是解决了的
快照读怎么解决幻读的问题的?
幻读:在一个读的事务里面,第一个select语句和第二个select语句之间有insert语句,导致两次select的结果不一样
通过间隙锁锁住一段范围,给select条件的范围加锁