MySQL系列文章:算法
『MySQL』搞懂 InnoDB 锁机制 以及 高并发下如何解决超卖问题数据库
『MySQL』MySQL执行流程bash
一张思惟导图看完系列文章:session
距离上一篇MySQL的文章已通过去一个月了,终于有时间来写写关于MySQL的事务了。本文内容默认是针对 MySQL InnoDB 引擎。数据结构
先来一张思惟导图读全文内容: 并发
了解事务以前,先来看看数据库为何须要有事务,假设没有事务会有什么影响?分布式
举一个转帐的例子,假设你朋友向你借10000元,你打开APP,乐呵呵的把钱转了,你的卡里已经少了10000元,可是你打电话给朋友时,你朋友说没有收到啊,你这时候确定卖银行怎么不靠谱,没到帐怎么把我卡里的钱给扣了。ide
咱们来捋一捋上述银行发生的过程,简单的分三步:
A发起转帐10000给B -> A银行卡减10000元 -> B银行卡增长10000元。
上述案例是第三步出现了问题,若是有事务,则不会发生案例中的事情,能够理解为事务就是这三个步骤是一根绳子上的蚂蚱,要么都成功,要么都失败。
因此数据库引入事务的主要目的是事务会把数据库会从一种一致状态转换到另外一种一致状态,数据库提交工做时能够确保要么全部修改都保存,要么全部修改都不保存。
了解事务,还须要了解事务的理论依据ACID,也能够说事务的几个特性。
仍是上面转帐的例子,原子性强调转帐从A-B的三个步骤必需要么都成功,要么都不成功。
原子性是整个数据库事务是不可分割的工做单位,只有事务中的全部的数据库操做都执行成功,才算整个事务成功。事务中任何一个SQL执行失败,已经执行成功的SQL语句也必须撤销,回到执行事务的以前的状态。
一致性是指事务将数据库从一种一致性状态变为下一种一致性状态。在事务开始以前和以后,数据库的完整性约束没有被破坏。
上面转帐的例子,不管转帐成功或者失败,A和B加起来变化就是10000元,不会多也不会少。
隔离性要求每一个读写事务对其余事务的操做对象能相互分离。
好比A转帐的银行是工商银行,那么别人在工商银行转帐不能干扰A的转帐行为。
持久性指事务一旦提交,其结果就是永久性的。
事务的实现就是如何实现ACID特性,下面一图下概况下:
由上图看,事务的实现经过 redo_log 和 undo_log, 以及锁实现,锁实现事务的隔离,上一篇已经讲解InnoDB的锁,须要了解的朋友能够查看上一篇文章。
redo_log 实现持久化和原子性,而undo_log实现一致性,二种日志都可以视为一种恢复操做,redo_log是恢复提交事务修改的页操做,而undo_log是回滚行记录到特定版本。两者记录的内容也不一样,redo_log是物理日志,记录页的物理修改操做,而undo_log是逻辑日志,根据每行记录进行记录。
redo_log 重作日志上面已经提到实现持久化和原子性,重作日志由两部分组成,一是内存中的重作日志缓存(redo log buffer),这部分是容易丢失的。二是重作日志文件(redo log file),这部分是持久的。
知道redo_log是什么?还须要了解其更新流程以及redo log存的是什么内容和恢复机制。
先来了解第一个问题,redo log的更新流程以下图,以一次Update 操做为例。
为了确保每第二天志都写入重作日志文件,InnoDB存储引擎会调用一次fsync操做。
了解redo log存储格式和内容以前,先来对比一下跟binlog二进制日志由什么不一样,binlog主要是主从复制和进行POINT-IN-TIME的恢复,想必你们对它不陌生。
binlog只有在事务提交的时候才会写入,且是数据库的上层产生的。redo log是Innodb引擎层产生的。
对比一两者的写入方式:
binlog是每次事务才写入,因此每一个事务只会有一条日志,记录的逻辑日志,也能够说记录的就是SQL语句。
redo log是事务开始就开始写入,*T1表示事务提交。记录的是物理格式日志,即每一个页的修改。
redo log默认是以block(块)的方式为单位进行存储,每一个块是512个字节。不一样的数据库引擎有对应的重作日志格式,Innodb的存储管理是基于页的,因此其重作日志也是基于页的。
redo log格式:
执行一条插入语句,重作日志大体为:
INSERT INTO user SELECT 1,2;
|
page(2,3), offset 32, value 1,2 # 主键索引
page(2,4), offset 64, value 2 # 辅助索引
复制代码
能够看到重作日志存储的格式有点看不太懂,看不懂没有关系,主要是告诉你们,重作日志存储物理格式日志,也就是基于存储页的修改。
再来了解一下 redo log的恢复机制:
上图概况了重作日志的恢复机制,先来解释一下图中出现的 LSN 是什么?
LSN(Log Sequence Number) 日志序列号,Innodb里,LSN占8个字节,且是单调递增的,表明的含义有: 重作日志写入的总量、checkpoint的位置、页的版本。
假设在LSN=10000的时候数据库出现故障,磁盘中checkpoint为10000,表示磁盘已经刷新到10000这个序列号,当前redolog的checkpoint是13000,则须要恢复10000-13000的数据。
再来想一想,redo log为何能够实现事务的原子性和持久性。
redo log一旦提交意味着持久化了,可是有时候须要对其进行rollback操做,那就须要undo log。
undo log是逻辑日志,只是将数据库逻辑的恢复到原来的样子。并不能将数据库物理地恢复到执行语句或者事务以前的样子。虽然全部的逻辑修改均被取消了,可是数据结构和页自己在回滚先后可能不同了。
既然是逻辑日志,能够理解为它存储的是SQL, 在事务中使用的每一条 INSERT 都对应了一条 DELETE,每一条 UPDATE 也都对应一条相反的 UPDATE 语句。
undo log 存放在数据库内部的一个特殊段(segment)中,也叫undo段,存在于共享表空间中。
undo log实现了事务的一致性,能够经过undo log恢复到事务以前的逻辑状态,保证一致性。
undo log 还能够实现MVCC(Multi-Version Concurrency Control ,多版本并发控制),多版本并发控制其实能够经过 undo log 造成一个事务执行过程当中的版本链,每个写操做会产生一个版本,数据库发生读的并发访问时,读操做访问版本链,返回最合适的结果直接返回。从而读写操做之间没有冲突,提升了性能。
上图列出了事务的一些控制语句,start transaction/begin、commit、rollback相信你们都有用过。
savepoint identifier 能够建立事务的一个保存点,执行回滚操做时能够回滚到指定保存点,不须要回滚整个事务。
打个比例,假设你去旅游到目标地须要三个行程,第一程 深圳到广州高铁,第二程 从广州飞到雅加达,第三程 雅加达飞到某岛。 若是再第三程 飞机取消行程,事务要回滚,若是要你再会深圳,你确定会心理一万个草泥马。由于再进入事务,第一步和第二步是不变的,因此不须要回滚,直接回滚第三步便可。
set transaction 修改事务隔离级别,好比修改会话级别的事务:
set session transaction isolation level read committed;
事务的隔离性是经过锁来实现,上一篇也提到事务的隔离级别,这篇简单回顾一下。
四种隔离级别,按READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE顺序,隔离级别是从低到高,InnoDB默认是REPEATABLE-READ级别,此级别在其他数据库中是会引发幻读问题,InnoDB采用Next-Key Lock锁算法避免了此问题,什么是幻读问题,请参考上一篇文章。
隔离级别越低,则事务请求的锁和保持锁的时间就越短。
READ-UNCOMMITTED 中文叫未提交读,即一个事务读到了另外一个未提交事务修改过的数据,整个过程以下图:
如上图,SessionA和SessionB分别开启一个事务,SessionB中的事务先将id为1的记录的name列更新为'lisi',而后Session 中的事务再去查询这条id为1的记录,那么在未提交读的隔离级别下,查询结果由'zhangsan'变成了'lisi',也就是说某个事务读到了另外一个未提交事务修改过的记录。可是若是SessionB中的事务稍后进行了回滚,那么SessionA中的事务至关于读到了一个不存在的数据,这种现象也称为脏读。
可见READ-UNCOMMITTED是很是不安全。
READ COMMITTED 中文叫已提交读,或者叫不可重复读。即一个事务能读到另外一个已经提交事务修改后的数据,若是其余事务均对该数据进行修改并提交,该事务也能查询到最新值。以下图:
在第4步 SessionB 修改后,若是未提交,SessionA是读不到,但SessionB一旦提交后,SessionA便可读到SessionB修改的内容。
从某种程度上已提交读是违反事务的隔离性的。
REPEATABLE READ 中文叫可重复读,即事务能读到另外一个已经提交的事务修改过的数据,可是第一次读过某条记录后,即便后面其余事务修改了该记录的值而且提交,该事务以后再读该条记录时,读到的还是第一次读到的值,而不是每次都读到不一样的数据。以下图:
InnoDB默认是这种隔离级别,SessionB不管怎么修改id=1的值,SessionA读到依然是本身开启事务第一次读到的内容。
SERIALIZABLE 叫串行化, 上面三种隔离级别能够进行 读-读 或者 读-写、写-读三种并发操做,而SERIALIZABLE不容许读-写,写-读的并发操做。 以下图:
SessionB 对 id=1 进行修改的时候,SessionA 读取id=1则须要等待 SessionB 提交事务。能够理解SessionB在更新的时候加了X锁。
分布式事务指容许多个独立的事务资源参与到一个全局的事务中。全局事务要求在其中的全部参与的事务要么都提交,要么都回滚。
InnoDB 是支持分布式事务,由一个或多个资源管理器(Resource Managers),一个事务管理器(Transaction Manager),以及一个应用程序(Application Program)组成。
以下图:
应用程序向一个或多个数据库执行事务操做,事务管理器进行管理事务,经过二段式提交,第一阶段全部参与的全局事务的节点都开始准备,告诉事务管理器都准备好了,能够提交了。第二阶段,事务管理器告诉每个资源管理器是执行Commit 仍是 Rollback。若是任何一个节点显示不能提交,则全部的节点被告知须要回滚。
InnoDB的分布式是数据库实现的,看看数据库外如何分布式事务,比较常见的是TCC分布式事务。
上图描述了TCC分布式事务的流程,假设电商业务中,支付后须要修改库存,积分,物流仓储的数据,若是一个失败则所有回滚。
TCC分布式事务,有三个阶段,Try,Confirm, Cancel。也就是说每一个参与事务的服务都须要实现这三个接口,库存、积分、仓储都须要实现这三个接口。
第一阶段,Try,业务应用调取各个服务的Try接口,告诉他们给我预留一个商品,有人要购买,能够理解为冻结,每一步都不执行成功,只是标记更新状态。
第二阶段,Confirm,确认阶段,即事务协调器调取每一个服务Confirm执行事务操做,若是某一个服务的Confirm失败,则有第三个阶段。若是成功则结束事务。
第三个阶段,Cancel,若是在第二个阶段有一个事务提交失败,则事务协调器调取全部业务的Cancel接口,回滚事务,将第一阶段冻结的商品恢复。
在MQ中间件链接的上游服务和下游服务中如何实现分布式事务了?
欢迎你们留言讨论。
更多MySQL相关文章和讨论,请关注公众号:『 天澄技术杂谈 』