Mysql系列(十二)—Msql之事务

目录

事务特性

A,原子性

C,一致性

I,隔离性

D,持久性

事务分类

事务日志

redo log

二进制日志和重做日志的区别

log block

log group

undo 

undo存储管理

undo log 格式

purge

group commit

事务控制语句

隐式提交的sql语句

事务操作的统计

事务隔离级别

分布式事务

内部XA事务


事务是数据库区别于文件系统的重要特性之一。事务会把数据库从一种状态变为另一种状态。在数据库提交工作时,可以确保要么所有的修改都已经保存了,要么所有修改都不保存。

InnoDB存储引擎完全符合ACID的特性。ACID分别为

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔离性(isolation)
  • 持久性(durability)

事务特性

A,原子性

原子性是指整个数据库事务是不可分割的工作单位,只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个语句的执行失败,已经执行的sql语句也必须撤销,数据库回滚事务之前的状态。

C,一致性

一致性指事务将数据库从一种状态转变为下一种的状态。在事务开始之前和事务 结束以后,数据库的完整性约束没有被破坏。

I,隔离性

事务的隔离性要求读写每个事务的对象对其他操作对象能相互分离。这类机制使用锁来实现。

D,持久性

事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能恢复。

事务分类

事务可以分为

  • 扁平事务
  • 带有保存点的扁平事务
  • 链事务
  • 嵌套事务
  • 分布式事务
事务类型特点缺点
扁平事务
  1. 使用最频繁
  2. 由begin work开始,由commit work或rollback work或者超时等因素结束
不能提交或者回滚事务的某一部分。例如:要从杭州到意大利坐飞机需要转机,那么每一段是不能单独锁定或者回滚的。
带有保存点的扁平事务
  1. 在扁平事务的基础上,支持分段回滚以及保存。扁平事务相当于事务开始有个保存点,整个事务的过程种也只有一次。
  2. 回滚可以选择回滚到哪个保存点(什么时候存在保存点可以savepoint aa来手动指定)

 

带有保存点的事务是易失的,当系统发生崩溃时,所有的保存点都将消失。
链事务
  1. 可以理解为保存点的另一种实现。
  2. 提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式的传给下一个要开始的事务。提交事务操作和开始下一个事务操作将合为一个原子操作。
回滚仅限于当前事务,即只能回滚到最近的一个保存点。
嵌套事务
  1. 嵌套事务是一个层次结构框架。由一个顶层事务控制各个层次的事务。
  2. 子树由若干事务组成,既可以是嵌套事务也可以是扁平事务。
  3. 位于根节点的事务称为顶层事务,其他事务称为子事务。处在叶子节点的事务为扁平事务。
  4. 子事务可以提交或回滚,但是不会马上生效,会等父事务提交后生效
  5. 任意一个事务的回滚都会影响其所有子事务的回滚,所以其不具有D的特性
  6. 在Moss的理论中,实际的工作由叶子节点来完成,只有叶子节点的事务才能访问数据库、发生消息、获取其他类型的资源。高层的事务仅负责逻辑控制。
innodb对于嵌套事务,并不原生支持,只能通过保持点来模拟。但是这样无法选择哪些锁需要被子事务继承,哪些被父事务保留。同时模拟并不支持在嵌套事务种并行的执行各个事务。

分布式事务

  1. 在一个分布式环境下运行的事务
  2. InnoDB提供了对XA事务的实现来支持分布式事务
  3. 分布式事务是通过java的JTA实现的
 

事务日志

redo log

重做日志用来实现事务的持久性,即ACID中的D.其由重做日志缓冲和重做日志文件组成

Innodb是事务的存储引擎,其通过Force Log at Commit 机制实现事务的持久性。当事务提交时,必须先将事务的所有日志写入到重做日志进行持久化。重做日志在Innodb中分为redo log和undo log。两者都是用来完成恢复操作。redo log用来保证事务的持久性,undo log用来帮助事务回滚以及MVCC的功能。redo log基本上都是顺序的,数据库运行过程中不需要对redo log的文件进行读取操作。undo log是随机读写的。

为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,Innodb需要调用一次fsync操作。为了确保重做日志写入磁盘,必须进行一次fsync操作。fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。

Innodb存储引擎允许用户手动设置非持久性的情况,当事务提交时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync操作。由于并非强制在事务提交时进行一次fsync操作,显然这可以显著提高数据库的性能。但是当数据库发生宕机的时候,由于部分日志未刷新到磁盘,因此会丢失最后一段时间的数据。

可以通过参数innodb_flush_log_at_trx_commit 来控制重做日志刷新到磁盘的策略。该参数默认为1,表示事务提交时必须调用一次fsync操作。还可以设置该值为0和2.0表示事务提交时不进行写入重做日志的操作,这个操作仅在master thread中完成,而在master thread中每1秒会进行一次重做日志文件的fsync操作。2表示事务提交时将重做日志写入文件,但仅写入文件系统的缓冲中,不进行fsync操作。mysql数据库发生宕机不会导致事物的丢失,但是操作系统宕机会导致事务丢失。

二进制日志和重做日志的区别

  1. 重做日志是在innodb存储引擎层产生的,二进制日志是在mysql服务层产生的。因此二进制日志所有存储引擎都会有,而重做日志只有innodb存储引擎存在。
  2. 两种日志记录的内容形式不同。二进制日志是一种逻辑日志,记录的是对应的sql语句。而innodb存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
  3. 两种日志记录写入磁盘的时间点不同。二进制日志只在事务提交完成后进行一次写入。重做日志在事务进行中不断被写入,其表现为日志并不是随事务提交的顺序进行写入的。

                                           

log block

在innodb存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓冲、重做日志文件都是以块的方式进行保存的,称为重做日志块,每块为512字节。如果一个页中产生的重做日志数量大于512字节,那么需要分割多个重做日志进行存储。每个重做日志块大小为512字节和磁盘扇区大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。

log group

log group为重做日志组,其中有多个重做日志文件。实际innodb只有一个log group。

log buffer 根据一定的规则将内存中的log block刷新到磁盘,具体规则为

  • 当事务提交时
  • 当log buffer中有一半的内存空间已经被使用时
  • log checkpoint时

undo 

重做日志记录了事务的行为,可以很好的通过其对页进行重做操作。但是事务有时还需要进行回滚操作,这时就需要undo日志。redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段,称为undo段。undo段位于共享表空间内。

undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子,所有的修改都被逻辑的取消了,但是数据结构和页本身在回滚之后可能大不相同。这是因为在多用户并发系统中,坑你会有数十、数百设置数千的并发事务。数据库的主要任务就说协调对数据记录的并发访问。比如,一个事务在修改当前页中某几条记录,而别的事务在对同一个页中另几条记录进行修改。因此,不能将一个事务回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。mysql的回滚操作是通过生成相反的sql来执行并完成回滚操作的。比如:一条insert 语句,回滚的时候会生成一条delete语句,update语句回滚的时候会生成修改为之前数据的update。

除了回滚操作,undo的另一个作用是mvcc,即innodb存储引擎中mvvc的实现是通过undo来完成的。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务会通过读取之前的行版本信息,一次实现非锁定读取。

注意:undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。

undo存储管理

innodb存储引擎对undo的管理同样采用段的方式。InnoDB存储引擎有rollback segment,每个回滚段种记录了1024个undo log segment。在InnoDB1.1版本之前,只有一个rollback segment,故其支持同时在线的事务限制为1024.从1.1版本开始,最大支持128个rollback segment,故其支持同时在线的事务提高到了128*1024.这些rollback segment都存在于共享表空间中。

事务在undo log segment分配页并写入undo log的过程中同样需要写重做日志。当事务提交时,InnoDB存储引擎做以下两件事:

  • 将undo log放入列表中,以供以后的purge操作
  • 判断undo log所在的页释放可以重用,若可以分配给下个事务使用

事务提交后不能马上删除undo log以及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。故事务提交时将undo log放入一个链表中,是否可以最终删除undo log以及undo log所在的页由purge线程来判断。

在innodb存储引擎中,对undo页可以进行重用。具体来说,当事务提交时,首先将undo log放入链表中,然后判断undo页的使用空间是否小于3/4,若是该undo页可以被重用,之后新的undo log记录在当前undo log的后面。由于存放undo log的列表是以记录进行组织的,而undo页可能存放这不同事务的undo log,因此purge操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程。

undo log 格式

在innodb存储引擎中,undo log分为:

  • insert undo log
  • update undo log

insert undo log 是指在insert 操作中产生的undo log。因为insert操作的记录,只对事务本身可见,对其他事务不可见,故该undo log可以在事务提交后直接删除。不需要进行purge操作。进行rollback操作时,根据记录事务的id,table_id,主键的列和值等信息定位到具体的记录,进行删除。

update undo log 记录的是对delete 和update操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。故提交时放入undo log链表,等待purge线程进行最后的删除。

因为update undo log要提供mvcc机制,所以需要存储的信息比insert undo log会多,例如之前修改的版本信息、DATA_TRZ_ID、DATA_ROLL_PTR等。

purge

delete 和update 操作可能并不直接删除原有的数据。

delete from t where a=1;

表t上列a有聚集索引,列b上有辅助索引。对于上述的delete操作,仅是将主键列等于1的记录delete flag设置为1,记录并没有被删除,即记录还是存在于B+树中。其次,对辅助索引上a=1,b=1的记录也没有做任何处理,也没有产生undo log。真正删除的操作其实被延时了,最终在purge操作中完成。

purge用于最终完成delete和update操作。若该记录已不被其他事务所引用,那么就可以进行真正的delete操作。可见,purge操作是清理之前的delete和update操作,将上述操作最终完成。

如下图:history list表示按照事务提交的顺序将undo log进行组织。在innodb 存储引擎设计中,先提交的事务总在尾端。undo page中存放了undo log,由于可以重用,因此一个undo page中可能存放了多个不同事务的undo log。trx5的灰色阴影表示该undo log还被其他事务引用。

执行过程:inoodb存储引擎首先从history list中找到第一个需要被清理的记录,这里为trx1,清理之后Innodb存储引擎会在trx1的undo log所在的页中继续寻找是否存在可以被清理的记录,这里会找到trx3,接着找到trx5,但是发现trx5被其他事务引用而不能去清理,故再去history list中查找,发现这时最尾端的记录为trx2,接着找到trx2所在的页,然后依次再把事务trx6、trx4的记录进行清理。此时,page2中的所有的页都被清理了,所以undo page可以被重用。

           

Innodb存储引擎这种先从history list中找undo log,然后再从undo page中找undo log的设计模式是为了避免大量的随机读取操作,从而提高purge的效率。

当innodb存储引擎的压力非常大时,并不能高效的进行purge操作。那么history list的长度会变得越来越长。全局动态参数innodb_max_purge_lag用来控制history list的长度,若长度大于该参数时,其会延缓DML的操作。其延缓算法为:

            

delay的单位为毫秒,要特别注意的是,delay的对象是行,而不是一个DML操作。当一个update操作需要更新5行数据时,每行数据的操作都会被delay,故总的延时时间为5*delay。delay的统计会在每次purge操作完成后,重新计算。

group commit

为了提高磁盘fsync的效率,当数据库都提供了group commit的功能,即一次fsync可以刷新多个事务日志进文件。对Innodb存储引擎来说,事务提交时会进行两个阶段的操作:

  • 修改内存中事务对应的信息,并将日志写入重做日志缓冲
  • 调用fsync将缓冲中的日志写入磁盘

第二步相对第一步来说是一个较慢的过程,这是因为存储引擎需要与磁盘打交道。但是当执行步骤二时,其他事务可以进行步骤1的操作,当再次执行步骤2时就可以多个事务的重做日志通过一次fsync刷新到磁盘。

然而在InnoDB1.2版本之前,在开启二进制日志后,InnoDB存储引擎的group commit功能会失效,从而导致性能的下降。为什么会导致这个问题呢?原因在于开启二进制日志后,为了保证存储引擎层的事务和二进制日志的一致性,二者之间使用了两阶段事务,具体如下:

  1. 事务提交时innodb存储引擎进行prepare操作
  2. mysql数据库上层写入二进制日志
  3. innodb存储引擎层将日志写入重做日志文件:a.修改内存中事务对应的信息,将日志写入重做日志缓冲b.调用fsync将日志都从重做日志缓冲写入磁盘。

为了保证mysql数据库上层二进制日志的写入顺序和innodb层的事务提交顺序一致,mysql数据库内部使用了prepare_commit_mutex这个锁。启用这个锁之后,步骤3中的a步骤不可以在b步骤执行时进行,从而导致了group commit失效。

为什么要保证mysql数据库上层二进制日志的写入顺序和innodb层的事务提交顺序一致呢?这是因为备份以及恢复的需要。例如下图事务T1的数据会丢失。因为在innodb层存储引擎会检测事务t3在上下两层都完了提交,不需要再进行恢复。因此通过锁来保证顺序性,然而锁会使group commit无法生效。

                     

                   

Mysql5.6采用了较为完美的解决办法。不但mysql数据库上层的二进制日志写入是group commit的,Innodb存储引擎层也是group commit的,此外还移除了锁,从而大大提高了整体性能。实现方式称为(Binary Log Group Commit)BLGC。

                           

实现:mysql数据库上层提交时首先按顺序写入队列中,队列中的第一个事务称为leader,其他事务称为follower,leader控制着follower的行为。BLGC的步骤分为三个阶段:

  • Flush阶段,将每个事务的二进制日志写入内存中
  • Sync阶段,将内存中的二进制日志刷新到磁盘,若队列中有多个事务,那么仅一次fsync操作就完成了二进制日志的写入,这就是BLGC
  • Commit阶段,leader根据顺序调用存储引擎层事务的提交,InnoDB存储引擎本身就支持group commit

当有一组事务在进行commit阶段时,其他新事务可以进行Flush阶段,从而使group commit不断生效。

事务控制语句

  • START TRANSACTION|BEGIN:显示的开启一个事务
  • COMMIT:提交事务
  • ROLLBACK:回滚事务
  • SAVEPOINT AAA:设置保存点aaa
  • RELEASE SAVEPOINT AAA:删除保存点aaa
  • ROLLBACK TO AAA:回滚到保存点aaa
  • SET TRANSACTION:设置事务的隔离级别

隐式提交的sql语句

执行完如下的sql,会有一个隐式的commit操作。

  • DDL语句: ALTER DATABASEUPGRADE DATA DIRECTORY NAME,ALTER EVENT, ALTER PROCEDURE, ALTER TABLE, ALTER VIEW,CREATE DATABASE, CREATE EVENT, CREATE INDEX, CREATE PROCEDURE, CREATE TABLE, CREATE TRIGGER, CREATE VIEW,DROP DATABASE, DROP EVENT, DROP INDEX, DROP PROCEDURE,DROP TABLE, DROP TRIGGER, DROP VIEW, RENAME TABLE,TRUNCATE TABLE。
  • 用来隐式地修改 MySQL架构的操作: CREATE USER、 DROP USER、 GRANT 、RENAME USER、 REVOKE、 SET PASSWORD。
  • 管理语句: ANALYZE TABLE、 CACHE INDEX、 CHECK TABLE、 LOAD INDEX  INTO CACHE、 OPTIMIZE TABLE、 REPAIR TABLE。

事务操作的统计

innodb需要考虑每秒请求数(QPS),每秒事务处理能力(TPS)。计算TPS的方法是(com_commit+com_rollback)/time,利用这种方式不会统计隐式的提交和回滚。变量值可以通过show global status like '***'查看。

事务隔离级别

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

 

分布式事务

innodb存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。XA事务由一个或多个资源管理器、一个事务管理器以及一个应用程序组成。

                  

分布式事务和本地事务不同的是,分布式事务需要多一次的prepare操作。也就是说,第一阶段,事务管理器向所有的事务发送prepare请求,所有的事务开始prepare,完成后告诉事务管理。第二阶段,事务管理器收到所有的正确响应后,执行commit或者rollback的命令。如果任何一个节点有问题都会导致所有事务的回滚。

java可以通过JTA(java Transaction API)来实现,简单实例如下:

可以通过innodb_support_xa来查看是否启用了XA事务(默认为ON)。

内部XA事务

在存储引擎与插件之间,或者在存储引擎与存储引擎之间,称为内部XA事务。最常见的XA事务存在于binlog 和innoDB之间。

如下图7-23:如果执行1、2之后步骤3之前发生了宕机,则会发生不一致的情况。为了解决这个问题采用了分布式事务,当事务提交时,Innodb会做一个prepare操作,将事务的xid写入,接着进行二进制日志的写入。如7-24,如果在innodb存储引擎提交前,mysql数据库宕机了,那么数据库在重启后会先坚持准备的UXID事务是否已经提交,若没有,则存储引擎会再进行一次提交操作。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值