数据库事务简介.

一、事务分类

事务是访问并更新数据库中各种数据项的一个程序执行单元,事务会把数据库从一种一致状态转换为另一种一致状态,这就是事务的目的,也是事务模型区别与文件系统的重要特性之一。

InnoDB 存储引擎中的事务(READ REPEATABLE 隔离级别)完全符合 ACID 的特性。ACID 是以下 4 个词的缩写:

  • 原子性(atomicity):数据库事务是不可分割的工作单位,事务中的数据库操作要么都成功,要么都不成功。
  • 一致性(consistency):将数据库从一种状态转变为下一种一致的状态,在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
  • 隔离性(isolation):每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。
  • 持久性(durability):事务一旦提交,其结果就是永久性的。

从事务理论的角度来说,可以把事务分为以下几种类型:

  • 扁平事务(Flat Transactions)
  • 带有保存点的扁平事务(Flat Transactions with Savepoints)
  • 链事务(Chained Transactions)
  • 嵌套事务(Nested Transactions)
  • 分布式事务(Distributed Transactions)

1. 扁平事务(Flat Transactions)

最简单,使用最频繁的事务,其间的操作是原子的,要么都执行,要么都回滚,也就是通常意义上我们理解的事务概念。

2. 带有保存点的扁平事务(Flat Transactions with Savepoints)

允许在事务执行过程中回滚到同一事务中较早的一个状态,保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

3. 链事务(Chained Transactions)

在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务,这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。

4. 嵌套事务(Nested Transactions)

由一个顶层事务(top-level transaction)控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。Moss 对嵌套事务这样描述:
1)嵌套事务是由若干事务组成的一颗树,子树既可以是嵌套事务,也可以是扁平事务。

2)处在叶节点的事务是扁平事务,但是每个子事务从根到叶节点的距离可以是不同的。

3)位于根节点的事务称为顶层事务,其他事务称为子事务,事务的前驱称为父事务(parent),事务的下一层称为儿子事务(child)。

4)子事务既可以提交也可以回滚,但是它的提交操作并不会马上生效,除非其父事务已经提交。

5)树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留 A、C、I 特性,不具有 D 的特性。

5. 分布式事务(Distributed Transactions)

分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中,全局事务要求在其中的所有参与的事务要么都提交,要么都回滚。

另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。

二、事务控制语句

在 MySQL 命令行的默认设置下,事务都是自动提交(auto commit)的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显示的控制一个事务,就要用到事务控制语句。

  • START TRANSACTION | BEGIN :显式地的开启一个事务;
  • COMMIT | COMMIT WORK:提交事务,并使得已对数据库做的所有修改成为永久性的;
  • ROLLBACK | ROLLBACK WORK:回滚用户的事务并撤销正在进行的所有未提交的事务;
  • SAVEPOINT [identifier]:创建一个保存点,一个事务中可以有多个 SAVEPOINT;
  • ROLLBACK TO [SAVEPOINT]:把事务回滚到标记点,而不回滚在此标记点之前的任何工作;
  • SET TRANSACTION:用来设置事务的隔离级别;

COMMIT 和 COMMIT WORK 语句基本上是一致的,都是用来提交事务,不同之处在于 COMMIT WORK 用来控制事务结束后的行为是 CHAIN 还是 RELEASE 的,该行为受 completion_type 参数控制。

SHOW VARIABLES LIKE '%completion_type%';

以下这些 SQL 语句会产生一个隐式的提交操作,即执行完这些语句后,会有一个隐式的 COMMIT 操作,即这些 SQL 语句执行完是不能被回滚的。

  • DDL 语句;
  • 管理语句:ANALYZE TABLE、CACHE INDEX、CHECK TABLE、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE;
  • 隐式修改 MySQL 架构的操作:CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD;

三、事务隔离级别

ISO 和 ANIS SQL 标准制定了四种事务隔离级别的标准,分别为:

  • Read Uncommitted 读未提交
  • Read Committed 读已提交
  • Repeatable Read 可重复读
  • Serializable 串行化

有关事务隔离级别的内容在 InnoDB 存储引擎中的锁 有讲解,可参考。

在 INNODB 存储引擎中,可以使用以下命令来设置当前会话或全局的事务隔离级别:

SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE]

据了解,大部分的用户质疑 Serializable 隔离级别带来的性能问题,但是根据 Jim Gray 在 《Transaction Processing》一书中指出,两者的开销几乎是一样的,甚至 Serializable 可能更优!!!因此在 InnoDB 存储引擎中选择 Repeatable Read 的事务隔离级别并不会有任何性能的损失。同样地,即使使用 Read Committed 的隔离级别,用户也不会得到性能的大幅度提升。

在 SERIALIZABLE 的事务隔离级别下,INNODB 存储引擎会对每个 SELECT 语句后自动加上 LOCK IN SHARE MODE,即为每个读取操作加一个共享锁。

四、分布式事务

XA(eXtended Architecture)是指由 X/Open 组织提出的分布式交易处理的规范。XA 是一个分布式事务协议,由Tuxedo 提出,所以分布式事务也称为 XA 事务。

XA 协议主要定义了事务管理器 TM(Transaction Manager,协调者)和资源管理器 RM(Resource Manager,参与者)之间的接口。其中,资源管理器往往由数据库实现,如 Oracle、DB2、MySQL,这些商业数据库都实现了 XA 接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

XA 事务由一个或多个资源管理器(RM)、一个事务管理器(TM)和一个应用程序(ApplicationProgram)组成。

XA 事务是基于两阶段提交(Two-phaseCommit,2PC)协议实现的,可以保证数据的强一致性,许多分布式关系型数据管理系统都采用此协议来完成分布式事务。阶段一为准备阶段,即所有的参与者准备执行事务并锁住需要的资源。当参与者 Ready 时,向 TM 汇报自己已经准备好。阶段二为提交阶段。当 TM 确认所有参与者都 Ready 后,向所有参与者发送 COMMIT 命令。

XA 事务的缺点是性能不好(协调者和参与者交互过于频繁),且无法满足高并发场景。一个数据库的事务和多个数据库间的 XA 事务性能会相差很多。因此,要尽量避免 XA 事务,如可以将数据写入本地,用高性能的消息系统分发数据,或使用数据库复制等技术,只有在其他办法都无法实现业务需求,且性能不是瓶颈时才使用 XA。

XA 事务在实际工程中使用较少,其理论价值大于实际意义。

SHOW VARIABLES LIKE 'innodb_support_xa';

除了 XA 可以解决分布式事务问题外,常见的还有 TCC、本地消费表、可靠消息最终一致性、尽最大努力通知等等,可以参考小米社区的这一篇文章:https://xiaomi-info.github.io/2020/01/02/distributed-transaction/

基于 MVCC 的分布式事务的方法为:为每个事务分配一个递增的事务编号,这个编号也代表了数据的版本号。当事务在各个节点上执行时,各个节点只需记录更新操作及事务编号,当事务在各个节点都完成后,在全局元信息中记录本次事务的编号。在读取数据时,先读取元信息中已成功的最大事务编号,再于各个节点上读取数据,只读取更新操作编号小于等于最后最大已成功提交事务编号的操作,并将这些操作应用到基础数据形成读取结果。

五、其它

  1. redo log 称为重做日志,恢复提交事务修改的页操作,用来保证事务的原子性和持久性;undo log 称为回滚日志,帮助回滚行记录到某个特定版本及 MVCC 的功能,用来保证事务的一致性;

  2. 参数 innodb_flush_log_at_trx_commit 用来控制重做日志刷新到磁盘的策略。该参数的默认值为 1,表示事务提交时必须调用一次 fsync 操作刷新到磁盘;0 表示事务提交时不进行重做日志刷新到磁盘操作,该操作仅在 master thread 中完成(mysql 宕机,未刷新到磁盘的数据会全部丢失);2 表示事务提交时,仅将重做日志写入文件系统的缓存中,不进行 fsync 操作(只要操作系统不宕机,数据就不会丢);

  3. 磁盘的 fsync 性能是有限的,为了提高磁盘 fsync 的效率,当前数据库都提供了 group commit 的功能,即一次 fsync 可以刷新确保多个事务日志被写入文件;

  4. 用户通常对 undo 有这样误解:undo 用于将数据库物理地恢复到执行语句或事务之前的样子 —— 但事实并非如此,undo 是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子;

  5. 最常见的 MySQL 内部的 XA 事务存在于 binlog 与 InnoDB 存储引擎之间,对于 binlog 日志和 InnoDB 存储引擎的 REDO 日志,必须同时写入,其就是用 XA 事务来保障的;

  6. 不要在循环中提交事务,而应该把循环当成一个事务;

  7. 长事务[Long-Lived Trascactions] 指的是执行时间较长的事务,对于长事务的问题,有时可以通过转化为小批量(mini batch)的事务来进行处理。

posted @ 2021-02-24 20:05  JMCui  阅读(194)  评论(0编辑  收藏  举报