1.什么是事务
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 事务由事务开始与事务结束之间执行的全部数据库操作组成。
事务具有四大特性(ACID),分别是:
- 原子性(Atomic):一个事务是一个不可再分的单元
- 一致性(Consistent):事务完成后,所有数据的状态都是一致的
- 隔离性(Isolation):如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离(也就是本章讨论的事物的隔离级别)
- 持久性(Duration):事务完成后,对数据库数据的修改被持久化存
在 MySQL 中,事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。
2.隔离级别
当数据库上有多个事务同时执行的时候,就可以能出现数据问题,隔离级别的出现,就是为了在性能与数据一致性上结合业务寻找的一种最优方案(隔离得越严实,效率就会越低)。
可能出现的数据问题:
脏读:读到了其他事务未提交的数据
可重复读:在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。
不可重复读:对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
幻读:幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。结合业务,选择合适的隔离级别可以解决数据问题。
- 读未提交 :一个事务还没提交时,它做的变更就能被别的事务看到
- 读提交:一个事务只有提交了,它做到变更才能被别的事物看见
- 可重复读(InnoDB默认):在同一个事物中,查询到的数据是一致的(不会受到其他事物的影响),在事物开始的数据建立快照读
- 串行化:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行
注意:在不同的隔离级别下,数据库行为是有所不同的。Oracle 数据库的默认隔离级别其实就是“读提交”,MySQL默认的是“可重复读”,迁移时要注意隔离级别的调整。
可以执行如下语句,查看数据库的隔离级别
show variables like 'transaction_isolation';
//有的版本是用tx_isolation
show variables like 'tx_isolation';
eg :不同隔离级别下看见的数据
3. InnoDB隔离级别实现的原理
对于不同的隔离级别,数据库采用了不同的处理方式:
- 读未提交 : 直接返回记录上的最新值
- 读提交 : 基于试图,在每个 SQL 语句开始执行的时候创建
- 可重复读 : 基于试图,在事务启动时创建的,整个事务存在期间都用这个视图(事务启动时的视图可以认为是静态的,不受其他事务更新的影响。)
- 串行化 : 用加锁的方式来避免并行访问
4. undo log
以下下内容转载于文章:【MySQL笔记】正确的理解MySQL的MVCC及实现原理_长路漫漫的歇脚处-CSDN博客
ps:本文很大的篇幅均转载自 大神:SnailMann的文章 《MySQL笔记】正确的理解MySQL的MVCC及实现原理》 仅以此文膜拜学习~~
undo log 的类型:
insert undo log 代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log 事务在进行update或delete时产生的undo log;
不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
对数据进行更新操作时,每次都会生成一条undo log,记录会以链表的形式(最新的一条修改在表头)存在rollback segment中旧记录链里。
InnoDB在建表时除了用户自定义的表字段,实际上还会帮我们生成一些字段用于MVCC的控制
DB_TRX_ID :
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
DB_ROLL_PTR:
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
一、比如一个有个事务插入persion表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL
二、现在来了一个事务1对该记录的name做出了修改,改为Tom
- 在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
- 然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
- 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
- 事务提交后,释放锁
三、 又来了个事务2修改person表的同一个记录,将age修改为30岁 - 在事务2修改该行数据时,数据库也先为该行加锁
- 然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
- 修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
- 事务提交,释放锁
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)
5. Read View 视图读
什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本
详情可以参考【MySQL笔记】正确的理解MySQL的MVCC及实现原理_长路漫漫的歇脚处-CSDN博客
6. MVCC 多版本并发控制
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。
mvcc的实现流程
假设有如下四个事务(四个事务的编号依次递增),现在来分析事务2的快照读如何生成:
-
当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务ID为2,此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为trx_list(系统的活跃列表 )
-
Read View不仅仅会通过一个列表trx_list来维护事务2执行快照读那刻系统正活跃的事务ID,还会有两个属性up_limit_id(记录trx_list列表中事务ID最小的ID),low_limit_id(记录trx_list列表中事务ID最大的ID,也有人说快照读那刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值+1,我更倾向于后者 >>>资料传送门 | 呵呵一笑百媚生的回答) ;所以在这里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,trx_list集合的值是1,3,Read View如下图
- 我们的例子中,只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,所以当前该行当前数据的undo log如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id和活跃事务ID列表(trx_list)进行比较,判断当前事务2能看到该记录的版本是哪个。
- 所以先拿该记录DB_TRX_ID字段记录的事务ID 4去跟Read View的的up_limit_id比较,看4是否小于up_limit_id(1),所以不符合条件,继续判断 4 是否大于等于 low_limit_id(5),也不符合条件,最后判断4是否处于trx_list中的活跃事务, 最后发现事务ID为4的事务不在当前活跃事务列表中, 符合可见性条件,所以事务4修改后提交的最新结果对事务2快照读时是可见的,所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本
- 也正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同
3.7 掌握度测试:
1.事务的概念是什么?
2.mysql的事务隔离级别读未提交, 读已提交, 可重复读, 串行各是什么意思?
3.读已提交, 可重复读是怎么通过视图构建实现的?
4.可重复读的使用场景举例?
5.事务隔离是怎么通过read-view(读视图)实现的?
6.并发版本控制(MCVV)的概念是什么, 是怎么实现的?
7.使用长事务的弊病? 为什么使用长事务可能拖垮整个库?
8.事务的启动方式有哪几种?
9.commit work and chain的语法是做什么用的?
10.怎么查询各个表中的长事务?
11.如何避免长事务的出现?
---------------------------------- 这是一个小尾巴哦~~~~
参考:
极客时间《MySQL实战45讲》
《【MySQL笔记】正确的理解MySQL的MVCC及实现原理》文章地址: https://blog.csdn.net/SnailMann/article/details/94724197
部分图片来源于网络,侵删~