事务原理:
为了支持事务,Innodb引入了下面几个概念:
一、MYSQL 日志介绍:
1.binlog
binlog常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的, 另外通过解析binlog能够实现mysql到其他数据源(如ElasticSearch)的数据复制。
2.redo log- 记录新数据的备份,保证事务持久性
当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。当Mysql失效重启进行恢复时重新执行redo log记录的SQL进行数据恢复。redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件。
3.undo log-保证事务的原子性 & 实现数据多版本MVCC
为了满足事务的原子性,在操作任何数据之前,首先将数据备份到Undo Log.然后再进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。undo log用于数据的回滚操作。
具体操作是 copy事务前的数据行到undo buffer,在适合的时间(事务提交前)把undo buffer中的内容刷新到磁盘。undo log记录了数据变更历史如,删除前备份 insert插入undo log中原行记录再在删除行记录上打删除标记,修改前备份 insert插入undo log中原行记录再修改更新行数据打标事务id&回滚指针(指向undo log中原行记录)。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。 与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间)。
二、mysql锁-实现隔离性
Innodb提供了行锁,分两种:排他锁、共享锁。
共享锁针对读,排他锁针对写,完全等同读写锁的概念。
排他锁: 写锁, 如果某个事务在更新某行,则其他事物无论是读还是写本行都必须等待;
共享锁: 读锁,如果某个事物读某行,则其他读的事物无需等待,而写事物则需等待。
事务隔离级别 - 隔离级别依次增强,但是导致的问题是并发能力的减弱
众所周知地是更新(update、insert、delete)是一个事务过程,在Innodb中,查询也是一个事务,只读事务。当读写事务并发访问同一行数据时,能读到什么样的内容则依赖事务级别:
- READ_UNCOMMITTED:读未提交-脏读- 事务能够看到其他事务没有提及的修改,当另一个事务又回滚了修改后的情况又被称为脏读dirty read
- READ_COMMITTED:读提交-幻读- 事务能够看到其他事务提交后的修改,会出现一个事务内两次读取数据可能因为其他事务提交的修改导致不一致的情况,称为不可重复读。
- REPEATABLE_READ:重复读 (默认级别)-每次都读取指定的版本,这样保证不会产生幻读,但可能读不到最新的数据
- SERIALIZABLE:串行化 -锁表,读写相互阻塞,使用较少
三、MYSQL事务原理:
Innodb ACID如何保证
• 原子性 redo + undo
• 一致性 redo
• 隔离性 锁 + MVCC
• 持久性 redo
下面演示下事务对某行记录的更新过程:
todo
1. 初始数据行
F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空。
2.事务1更改该行的各字段的值
当事务1更改该行的值时,会进行如下操作:
用排他锁锁定该行
记录redo log
把该行修改前的值Copy到undo log,即上图中下面的行
修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
commit 提交事务 / rollback回滚事务(需要根据当前回滚指针从undo log中找出事务修改前的行记录版本,并恢复。)
上述过程确切地说是描述了UPDATE的事务过程,Undo log分为Insert和Update两种,delete可以看做是一种特殊的update,即在记录上修改删除标记。
3.事务2修改该行的值
与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。todo delete
因此,如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的时在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。todo
四、MYSQL到底是怎么实现MVCC的?
在Mysql中MVCC是在Innodb存储引擎中得到支持的,Innodb为每行记录都实现了三个隐藏字段:
6字节的事务ID(DB_TRX_ID)-标识该行所属的事务、
7字节的回滚指针(DB_ROLL_PTR)、
隐藏的ID
innodb存储的最基本row中包含一些额外的存储信息 DATA_TRX_ID(标识该行所属的事务),DATA_ROLL_PTR(回滚指针),DB_ROW_ID(隐藏id),DELETE BIT(删除标志)
6字节的DATA_TRX_ID 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1
7字节的DATA_ROLL_PTR 指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针
6字节的DB_ROW_ID,当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值.,这个用于索引当中
DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候
MYSQL如何实现事务?
MYSQL读写锁:读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥,提升并发读能力。为了进一步提升并发能力,实现读写之间也不冲突,读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据。
MVCC原理:
• 历史版本存储在UNDO Log空间
• 每条记录带版本号及回滚指针
• 废弃记录异步purge
MVCC实现
新增一个事务时事务id会增加,trx_id能够表示事务开始的先后顺序。
update undo log记录了数据之前的数据信息,通过这些信息可以还原到之前版本的状态。
当进行插入操作时,生成的Insert undo log在事务提交后即可删除,因为其他事务不需要这个undo log。
进行删除修改操作时,会生成对应的undo log,并将当前数据记录中的db_roll_ptr指向新的undo log
---
MVCC概念: 提升事务并发处理能力
1、MVCC机制是行级锁的一种妥协,多线程事务读取时,避免使用锁,而是采用一种更小的开销,允许非阻塞读取,写操作进行时只锁定必要的记录
2、简单的实现方式:MVCC保存某个时间点上的数据快照。一个事务内,看到的是同一个版本的快照,数据一致。不同事务在同一时间点看到的数据会不一致,因为他们得到的数据版本不一样。
MVCC可以任务它是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。实现了非阻塞的读操作提高db并发能力,写操作也只锁定必要的行。
MVCC的基本原理: 非理想态MVCC,提供读的非阻塞
MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论执行多长时间,在同一个事务里看到数据都是一致的。根据事务开始的时间不同,每个事务对同一张表同一个时刻看到的数据可能不同。
MVCC的基本特征:
每行数据都存在一个版本,每次数据更新时都更新该版本。
修改时Copy出当前版本随意修改,各个事务之间无干扰。
保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)
就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道,而Innodb的实现方式是:
事务以排他锁的形式修改原始数据
把修改前的数据存放于undo log,通过回滚指针与主数据关联
修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
----
InnoDB存储引擎MVCC的实现策略:
通过在每一行数据后面保存两个隐藏的列实现:当前行创建时的版本号和删除时的版本号(可能为空)。这里的版本号并不是实际的时间值,而是系统版本号(可以理解为事务的ID)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。具体做法见下面的示意图。
MVCC具体的操作如下:
SELECT:InnoDB会根据以下两个条件检查[需同时满足]每行记录:
1)InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
2)行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE:InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE:InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统的版本号为原来的行作为删除标识。
说明
insert操作时 “创建时间”=DB_ROW_ID,这时,“删除时间 ”是未定义的;
update时,复制新增行的“创建时间”=DB_ROW_ID,删除时间未定义,旧数据行“创建时间”不变,删除时间=该事务的DB_ROW_ID;
delete操作,相应数据行的“创建时间”不变,删除时间=该事务的DB_ROW_ID;
select操作对两者都不修改,只读相应的数据
保存这两个额外系统版本号,使大多数操作都可以不用加锁。这样设计使得计数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
MVCC只在REPEATABLE READ和READ COMMITED两个隔离级别下工作,其它两个隔离级别和MVCC不兼容。
---
参考:
http://blog.csdn.net/chen77716/article/details/6742128
https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/
http://www.cnblogs.com/chenpingzhao/p/5065316.html
http://zhangxiong0301.iteye.com/blog/2230193