一、MySQL事务
事务就是一组原子性的SQL执行单元
。
如果数据库引擎能够成功地对数据库应 用该组査询的全部语句,那么就执行该组SQL。如果其中有任何一条语句因为崩溃或其 他原因无法执行,那么所有的语句都不会执行。要么全部执行成功(commit),要么全部执行失败(rollback)。
1.
ACID四大特性
- 原子性(Atomicity)
单个事务,为一个不可分割的最小工作单元,整个事务中的所有操作要么全部commit成功,要么全部失败rollback,对于一个事务来说,不可能只执行其中的一部分SQL操作,这就是事务的原子性。
- 一致性(Consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态。
- 隔离性(Isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。
- 持久性(Durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。
事务的ACID特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难, 甚至可以说是不可能完成的任务。一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保ACID的实现。
2.如何具体实现ACID
对MySQL来说,逻辑备份日志(binlog)、重做日志(redolog)、回滚日志(undolog)、锁技术 + MVCC就是MySQL实现事务的基础。
原子性:通过undolog来实现。
持久性:通过binlog、redolog来实现。
隔离性:通过(读写锁+MVCC)来实现。
一致性:MySQL通过原子性,持久性,隔离性最终实现(或者说定义)数据一致性。
1、原子性原理
事务通常是以BEGIN TRANSACTION 开始,以 COMMIT 或 ROLLBACK 结束。
COMMIT 表示提交,即提交事务的所有操作并持久化到数据库中。
ROLLBACK表示回滚,即在事务中运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库所有已完成的操作全部撤销,回滚到事务开始时的状态,这里的操作指对数据库的更新操作,已执行的查询操作不用管。这时候也就需要用到 undolog 来进行回滚。
undolog:
每条数据变更(INSERT/UPDATE/DELETE/REPLACE)等操作都会生成一条undolog记录,在SQL执行前先于数据持久化到磁盘。
当事务需要回滚时,MySQL会根据回滚日志对事务中已执行的SQL做逆向操作,比如 DELETE 掉一行数据的逆向操作就是再把这行数据 INSERT回去,其他操作同理。
2、持久性原理
先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘 IO,然而即使是使用 SSD 磁盘 IO 也是非常消耗性能的。为此,为了提升性能 InnoDB 提供了缓冲池(Buffer Pool),Buffer Pool 中包含了磁盘数据页的映射,可以当做缓存来使用:
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;
我们知道,MySQL表数据是持久化到磁盘中的,但如果所有操作都去操作磁盘,等并发上来了,那处理速度谁都吃不消,因此引入了缓冲池(Buffer Pool)的概念。
Buffer Pool 中包含了磁盘中部分数据页的映射,可以当做缓存来用;这样当修改表数据时,我们把操作记录先写到Buffer Pool中,并标记事务已完成,等MySQL空闲时,再把更新操作持久化到磁盘里(你可能会问,到底什么时候执行持久化呢?1、MySQL线程低于高水位;2、当有其他查询、更新语句操作该数据页时),从而大大缓解了MySQL并发压力。
但是它也带来了新的问题,当MySQL系统宕机,断电时Buffer Pool数据不就丢了?
因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。
于是 redo log + binlog的经典组合就登场了。
在 MySQL 里有这样的问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了WAL 的技术。
WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。
具体来说,当有一条update语句要执行的时候,InnoDB 引擎就会先把记录写到 redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为
crash-safe
。本质上说,crash-safe 就是落盘处理,将数据存储到了磁盘上,断电重启也不会丢失。
redo log 是 InnoDB 引擎特有的日志
,而 Server 层也有自己的日志,称为binlog(归档日志),其实就是用来恢复数据用的。
3、隔离性原理
隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。
级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。
前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的正确、可用,比如要做到宕机后的恢复、事务的回滚等,保证数据是正确可用的!
那么隔离性是要做到什么呢?
隔离性要管理的是:多个并发读写请求(事务)过来时的执行顺序。像交警在马路口儿指挥交通一样,当并发处理多个DML更新操作时,如何让事务操作他该看到的数据,出现多个事务处理同一条数据时,让事务该排队的排队,别插队捣乱,保证数据和事务的相对隔离,这就是隔离性要干的事儿。
所以,从隔离性的实现原理上,我们可以看出这是一场数据的可靠性与性能之间的权衡。
4、一致性原理
一致性,我们要保障的是数据一致性,数据库中的增删改操作,使数据库不断从一个一致性的状态转移到另一个一致性的状态。
事务该回滚的回滚,该提交的提交,提交后该持久化磁盘的持久化磁盘,该写缓冲池的写缓冲池+写日志;对于数据可见性,通过四种隔离级别进行控制,使得库表中的有效数据范围可控,保证业务数据的正确性的前提下,进而提高并发程度,支撑服务高QPS的稳定运行,保证数据的一致性,这就是咱们叨叨叨说的清楚想不明白的数据库ACID四大特性。
二、并发场景下事务存在的数据问题
下面我们介绍一下脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)这三类并发问题,以及每种问题出现的原理及场景。
1. 脏读(针对的是未提交读数据)
事务A修改了数据,但未提交,而事务B查询了事务A修改过却没有提交的数据,这就是脏读,因为事务A可能会回滚。
2. 不可重复读(针对其他提交前后,读取数据本身的对比)
事务A 先 查询了工资金额,是3000块钱,未提交 。事务B在事务A查询完之后,修改了工资金额,变成了13000, 在事务A前提交了;如果此时事务A再查询一次数据,就会发现钱跟上一次查询不一致,是13000,而不是3000。
这就是不可重复读。强调事务A对要操作的数据被别人修改了,但在不知请的情况下拿去做之前的用途。
对于不可重复读,说简单点就是同一个事物内,查到的结果都不一致,就失去了MySQL的“一致性”,这是很严重的错误。你想,如果财务大姐没有二次确认,而是直接以第一次查询为准,又给我加了1万怎么办?想想还有点小激动呢。
3. 幻读(针对其他提交前后,读取数据条数的对比)
幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般情况下只新增。
事务A先修改了某个表的所有纪录的状态字段为已处理,未提交;事务B也在此时新增了一条未处理的记录,并提交了;事务A随后查询记录,却发现有一条记录是未处理的,很是诧异,刚刚不是全部修改为已处理嘛,以为出现了幻觉,这就是幻读。
- 脏读说的是事务知道了自己本不应该知道的东西,强调的动作是查询,我看到了自己不该看的东西 ;
- 不可重复读强调的是一个人查的时候,其他人却可以增删改, 但我却不知道数据被改了,还拿去做了之前的用途;
- 幻读强调的是我修改了数据,等我要查的时候,却发现有我没有修改的记录,为什么,因为有其他人插了一条新的。
三、隔离级别概述
为了解决上述问题,MySQL制定了四种不同的“隔离级别”,包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
隔离级别 | 效果 |
读未提交(RU) | 一个事务还没提交时,它做的变更就能被别的事务看到。(别的事务指同一时间进行的增删改查操作)。 |
读提交(RC) | 一个事务提交(commit)之后,它做的变更才会被其他事务看到。 |
可重复读(RR) | 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 |
串行(xíng)化(S) | 正如物理书上写的,串行是单线路,顾名思义在MySQL中同一时刻只允许单个事务执行,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 |
各个隔离级别下解决的问题如下
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted | × | × | × |
Read Committed | √ | × | × |
Repeat Read | √ | √ | ×(MySQLInnoDB可解决) |
串行化 | √ | √ | √ |
原理描述
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在MySQL默认的隔离级别“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
文章参考