一,事务概述
1,为什么需要事务
想象一个场景:A 通过银行向 B 转账。银行应该这样做:
- 从 A 账户上减少一笔钱
- 向 B 账户上增加同样数量的一笔钱
看起来其实够简单,但中途可能存在多种意外导致这次转账失败,比如银行突然停电、转账系统被攻击后崩溃等等,这就会导致一些问题,比如 A 账户上钱被划走了但没到 B 的账户、A 账户上钱没被划走但 B 的账户收到了钱等等,总之就是会出现不应该出现的状况。而且因为银行的转账系统是多人使用,考虑到意外情况,不确定的结果就更加复杂。
显然,简单地将数据库操作中的状态转换映射到现实中的状态转换是不容易的,因此我们需要一种机制来保障这种状态转换的准确性。这种机制就是事务管理。
这里的事务就是一组用户定义的数据库操作序列,是一个不可分割的最小工作单位。
2,事务的 ACID 特性
事务有着极其严格的定义,它必须同时满足四个特性:
- 原子性(Atomicity)。
- 一致性(Consistency)。
- 隔离性(Isolation)。
- 持久性(Durability)。
(1)原子性
原子性要求事务的所有的操作,要么全部完成,要么全不完成。
因为一个事务往往包含多个操作,比如一个取款的流程为:
- 登录ATM机平台,验证密码。
- 从远程银行的数据库中,取得账户的信息。
- 用户在ATM机上输入欲提取的金额。
- 从远程银行的数据库中,更新账户信息。
- ATM机出款。
- 用户取钱。
整个取款的操作过程应该视为原子操作,要么都做,要么都不做。
只有使事务中所有的数据库操作都执行成功,才算整个事务成功。如果事务中任何一个 SQL 语句执行失败,那么已经执行成功的 SQL 语句都必须撤销,并让数据库状态应该退回到执行事务前的状态。
如果事务中的操作都是只读的,要保持原子性是很简单的。一旦发生任何错误,要么重试,要么返回错误代码。因为只读操作不会改变系统中的任何相关部分。 但是,当事务中的操作需要改变系统中的状态时,例如插入、更新或删除记录,情况可能就不像只读操作那么简单了。如果操作失败,很有可能引起状态的变化,因此必须要保护系统中并发用户访问受影响的部分数据。
(2)一致性
一致性要求事务的结果使得数据库从一个一致性状态转变为另一个一致性状态。
比如在表中有一个存在唯一约束的字段,如果在一个事务完成后,这个字段变得非唯一了,这就破坏了事务的一致性要求,即事务将数据库从一种一致性状态变为了一种不一致性状态。
事务被提交后有两种结果,一种是成功完成,另一种是未完成,但做的操作都必须被撤销以返回执行事务之前的状态。只有成功完成或撤销才能保证结果状态一致。
(3)隔离性
隔离性要求每个事务的读写对象与其他事务的读写对象能相互隔离的、互不可见的。
比如在表中有一个存在一个表示商品数量的字段,假设最初的值为10,A 看到剩余 10 个就打算买 2 个则应该只剩 8 个,且几乎同时的稍后一点时刻 B 看到剩余 8 个就打算买 8 个则应该剩 0 个,最后其他用户看到只剩 0 个后放弃购买打算。总之,不能说 A 买 2 个,B 还能买 10 个,C 再买 2 个,最后导致剩余 -4 的情况。
隔离性在多用户并发操作同一资源时相当重要,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。
(4)持久性
持久性要求事务应该永久保留其结束后产生的状态转换的结果。
两人相互转账成功后的结果应该是能后被持久化的,不会因为时间的变化而变化,也不会因为修复了转账后崩溃的数据库而导致各自的余额变化。
持久性是保证事务系统的高可靠性的重要部分,与其他工具配合才能完成高可用性。
ACID 都讲完之后,再来看看事务的定义:事务(Transaction)就是需要保证原子性、隔离性、一致性和持久性的一个或多个数据库操作。
3,事务所处的状态与分类
根据事务中的操作所在的不同执行阶段,可以把事务大致划分成五个状态:
- 活动的(active):事务对应的数据库操作正在执行过程中。
- 部分提交的(partiaIly committed):事务中的最后一个操作执行完成,但由于操作都在内存中执行,所操作的结果并没有刷新到磁盘。
- 失败的(failed):事务处于活动的状态或者部分提交的状态 时,可能遇到了某些意外而无法继续执行,或者入为停止了当前事务的执行。
- 中止的(aborted): 当因为事务未执行完而失败时,将已经做的操作都撤销以恢复到最初的数据状态。
- 提交的(committed):一个处于部分提交的状态的事务将修改过的数据都刷新到磁盘中。
从事务理论的角度来说,可以把事务分为以下五种类型:
-
扁平事务(Flat Transaction):所有操作都处于同一层次,要么都执行,要么都回滚。 -
带有保存点的扁平事务(Flat Transactions with Savepoints):在扁平事务的基础上添加保存点(Savepoint)来通知系统记住事务当前的状态,以便当之后发生错误时事务能回到保存点当时的状态。 -
链事务(Chained Transactions):在带有保存点的扁平事务的基础上以保存点为分割点,将事务拆分为更轻量的事务,通过一个事务提交触发下一个事务的开始。 -
嵌套事务(Nested Transactions):将链式结构转为树状结构,各个轻量级的扁平事务作为叶结点,并统一由作为根节点的顶层事务进行逻辑控制。 -
分布式事务(Distributed Transactions):需要在分布式环境下相互配合才能运行的扁平事务。
二,简单使用事务管理机制
InnoDB 存储引擎中的事务完全符合 ACID 的特性,这里就使用InnoDB 存储引擎。
1,开启事务
可以使用下面两种语句来开启一个事务.
BEGIN [WORK];
或
START TRANSACTION [ OPTIONS ];
- READ ONLY:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
- READ WR1TE:标识当前事务是一个读写事务。
- WlTH CONSISTENT SNAPSHOT:启动一致性读。
举个例子🌰:开始事务
mysql> USE bank;
Database changed
mysql> SHOW TABLES;
+
| Tables_in_bank |
+
| account |
+
1 row in set (0.00 sec)
mysql> DESC account;
+
| Field | Type | Null | Key | Default | Extra |
+
| name | varchar(10) | NO | PRI | NULL | |
| amount | int | YES | | NULL | |
+
2 rows in set (0.00 sec)
mysql> SELECT * FROM account;
+
| name | amount |
+
| A | 100 |
| N | 200 |
+
2 rows in set (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT amount INTO @a.amt FROM account WHERE name='A';
Query OK, 1 row affected (0.00 sec)
mysql> UPDATE account SET amount=@a.amt-10 WHERE name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT amount INTO @b.amt FROM account WHERE name='B';
Query OK, 0 rows affected, 1 warning (0.12 sec)
mysql> UPDATE account SET amount=@b.amt+10 WHERE name='B';
Query OK, 0 rows affected (1.62 sec)
Rows matched: 0 Changed: 0 Warnings: 0
2,提交事务
如果事务成功完成,则需要使用 COMMIT 语句进行提交,以确保事务结果被持久化。
- 在 Inno DB中,所有用户的每条单独的语句都发生在事务中。默认情况下,MySQL 为每个启用的新连接启动 autocommit (可通过
SET autocommit = 0 关闭),因此如果该语句没有返回错误,MySQL 会在每个 SQL 语句之后执行自动提交。
mysql> show variables like "autocommit";
+
| Variable_name | Value |
+
| autocommit | ON |
+
1 row in set, 1 warning (0.00 sec)
- 在事务处理中,提交不会隐含地进行,需要显式地使用
COMMIT 语句进行提交。
举个例子🌰:提交事务
mysql> COMMIT;
Query OK, 0 rows affected (1.90 sec)
以下这些 SQL 语句会产生一个隐式的提交操作:
- DDL语句:ALTER DATABASE…UPGRADE 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。
3,使用保存点
在事务中可以通过 SAVEPOINT 语句指定事务的一个保存点,然后可以通过 ROLLBACK TO 语句回滚到指定的保存点。可以通过 RELEASE 语句释放保存点。
- 对于复杂的事务,可以定义多个不同的保存点以在不同的条件下恢复到不同的状态。
举个例子🌰:使用保存点
mysql> SELECT * FROM account;
+
| name | amount |
+
| A | 100 |
| B | 200 |
+
2 rows in set (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT amount INTO @a.amt FROM account WHERE name='A';
Query OK, 1 row affected (0.00 sec)
mysql> UPDATE account SET amount=@a.amt-10 WHERE name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SAVEPOINT decr_10_from_A;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT amount INTO @b.amt FROM account WHERE name='B';
Query OK, 1 row affected (0.00 sec)
mysql> UPDATE account SET amount=@b.amt+10 WHERE name='B';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SAVEPOINT incr_10_to_B;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM account;
+
| name | amount |
+
| A | 90 |
| B | 210 |
+
2 rows in set (0.00 sec)
mysql> UPDATE account SET amount=@b.amt+30 WHERE name='B';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM account;
+
| name | amount |
+
| A | 90 |
| B | 230 |
+
2 rows in set (0.00 sec)
mysql> ROLLBACK TO incr_10_to_B;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM account;
+
| name | amount |
+
| A | 90 |
| B | 210 |
+
2 rows in set (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.16 sec)
mysql> SELECT * FROM account;
+
| name | amount |
+
| A | 90 |
| B | 210 |
+
2 rows in set (0.00 sec)
4,回滚事务
如果在事务还未结束之前发现某条操作语句有误,则可以使用 ROLLBACK 语句回滚事务,从而使数据库恢复到事务执行之前的状态。
5,对于事务操作的统计
由于 InnoDB 存储引擎是支持事务的,因此在考虑每秒请求数(Question Per Second,QPS)的同时,应该关注每秒事务处理的能力(Transaction Per Second,TPS)。
计算 TPS 的方法是 (com_commit+com_rollback) / time。
- 但是利用这种方法进行计算的前提是:所有的事务必须都是显式提交的,如果存在隐式地提交和回滚(默认autocommit=1),不会计算到 com_commit 和 com_rollback 变量中。
mysql>SHOW GLOBAL STATUS LIKE'com_commit'\G;
***************************1.row***************************
Variable_name:Com_commit
Value:5
1 row in set(0.00 sec)
mysql>INSERT INTO t SELECT 3;
Query OK,1 row affected(0.00 sec)
Records:1 Duplicates:0 Warnings:0
mysql>SELECT*FROM t\G;
***************************1.row***************************
a:1
***************************2.row***************************
a:2
***************************3.row***************************
a:3
3 rows in set(0.00 sec)
mysql>SHOW GLOBAL STATUS LIKE'com_commit'\G;
***************************1.row***************************
Variable_name:Com_commit
Value:5
1 row in set(0.00 sec)
三,事务并发问题及隔离级别简介
想象一种情况:在多人操作的 MySQL 数据库中有两个事务:
- 事务一删除了表中的一行,但未结束事务。
- 事务二紧接着来查询被事务一删除的那样的数据。那么事务二会获得怎样的查询结果呢?
事务一和事务二就是是一个路口上行驶的车,路口就是两者操作的数据: 在多个事务同时操作一项资源时,可能产生以下问题:
1,脏读、不可重复读、幻读
脏读:一个事务读到了另一个未提交的事务修改过的数据。
不可重复读:一个事务只能读到另一个已经提交的事务修改后的数据即最新值,但需要读的是未修改的数据。
幻读:一个事务先根据某些条件查询一些记录但因为不存在而读不出来,就准备插入相关记录,但在插入完成之前,另一个事务已经向表中插入了符合这些条件的记录,原先的事务会因为记录已存在而无法插入,看起来第一次读就像产生了读不出来的幻觉一样。
mysql 幻读的详解、实例及解决办法 事务并发可能出现的情况
2,四个隔离级别
如何保证两辆车安全地通过路口?这就诞生了事务隔离机制,事务隔离机制提供了四中隔离级别来划分一个事务与其他事务在资源或数据修改方面的隔离程度。
SQL标准定义的四个隔离级别为:
- 未提交的读(READ UNCOMMITTED):事务可以读到其他事务写入但未提交的记录。
- 已提交的读(READ COMMITTED):事务可以读到其他事务写入并已提交的记录。
- 可重复的读(REPEATABLE READ):保证同一事务读到相同的结果,无论在此期间是否有别的事务对数据进行了修改。
- 序列化(SERIALIZABLE):最高的隔离级别,它通过强制事务排序(使用锁),使之不可能相互冲突。
各个隔离级别能解决哪些问题:
官方文档:15.7.2.1 Transaction Isolation Levels MySQL的四种事务隔离级别及实例演示
3,使用隔离级别
1,查看当前会话的隔离级别:
mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+
| Variable_name | Value |
+
| transaction_isolation | REPEATABLE-READ |
+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@transaction_isolation;
+
| @@transaction_isolation |
+
| REPEATABLE-READ |
+
1 row in set (0.00 sec)
2,设置当前会话的隔离级别:
使用语句:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
level: {
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
或命令:
官方文档:设置隔离级别:13.3.7 SET TRANSACTION Statement
|