mysql中的事务控制和锁定语句
摘自书本
MySQL 支持对 MyISAM 和 MEMORY 存储引擎的表进行表级锁定,对 BDB 存储引擎的表进行
页级锁定,对 InnoDB 存储引擎的表进行行级锁定。默认情况下,表锁和行锁都是自动获得
的,不需要额外的命令。但是在有的情况下,用户需要明确地进行锁表或者进行事务的控制,
以便确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成。
有关锁机制、不同存储引擎对锁的处理、死锁等内容,将会在后面的优化篇中进行更详细的
介绍,有兴趣的读者可以参见相关的章节。
14.1 LOCK TABLE 和 UNLOCK TABLE
LOCK TABLES 可以锁定用于当前线程的表。如果表被其他线程锁定,则当前线程会等待,直
到可以获取所有锁定为止。
UNLOCK TABLES 可以释放当前线程获得的任何锁定。当前线程执行另一个 LOCK TABLES 时,
或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐含地解锁,具体语法如下:
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...Linux
公社
www.linuxidc.com
171
UNLOCK TABLES
如表 14-1 所示是一个获得表锁和释放表锁的简单例子,演示的是 film_text 表获得 read 锁的
情况,其他 session 更新该表记录会等待锁,film_text 表释放锁以后,其他 session 可以进行
更新操作。其中 session1 和 session2 表示两个同时打开的 session,表格中的每一行表示同
一时刻两个 session 的运行状况,后面的例子也都是同样格式,不再重复说明。
表 14-1 一个获得表锁和释放表锁的简单例子
session_1
session_2
获得表 film_text 的 READ 锁定
mysql> lock table film_text read;
Query OK, 0 rows affected (0.00 sec)
当前 session 可以查询该表记录
mysql> select film_id,title from film_text
where film_id = 1001;
+---------+------------------+
| film_id | title |
+---------+------------------+
| 1001 | ACADEMY DINOSAUR |
+---------+------------------+
1 row in set (0.00 sec)
其他 session 也可以查询该表的记录
mysql> select film_id,title from film_text where
film_id = 1001;
+---------+------------------+
| film_id | title |
+---------+------------------+
| 1001 | ACADEMY DINOSAUR |
+---------+------------------+
1 row in set (0.00 sec)
其他 session 更新锁定表会等待获得锁:
mysql> update film_text set title = 'Test' where film_id =
1001;
等待
释放锁
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
等待
Session 获得锁,更新操作完成:
mysql> update film_text set title = 'Test' where film_id =
1001;
Query OK, 1 row affected (1 min 0.71 sec)
Rows matched: 1 Changed: 1 Warnings: 0
有关表锁的使用,将在后面的章节中进行介绍,读者可以参见“20.2 Myisam 表锁”一节以
获得更详细的信息。
14.2 事务控制
MySQL 通过 SET AUTOCOMMIT、START TRANSACTION、COMMIT 和 ROLLBACK 等语句支
持本地事务,具体语法如下。
START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}Linux
公社
www.linuxidc.com
172
默认情况下,MySQL 是自动提交(Autocommit)的,如果需要通过明确的 Commit 和
Rollback 来提交和回滚事务,那么需要通过明确的事务控制命令来开始事务,这是和 Oracle
的事务管理明显不同的地方。如果应用是从 Oracle 数据库迁移到 MySQL 数据库,则需要确
保应用中是否对事务进行了明确的管理。
START TRANSACTION 或 BEGIN 语句可以开始一项新的事务。
COMMIT 和 ROLLBACK 用来提交或者回滚事务。
CHAIN 和 RELEASE 子句分别用来定义在事务提交或者回滚之后的操作,CHAIN 会立
即启动一个新事物,并且和刚才的事务具有相同的隔离级别,RELEASE 则会断开和客户端的
连接。
SET AUTOCOMMIT 可以修改当前连接的提交方式,如果设置了 SET AUTOCOMMIT=0,
则设置之后的所有事务都需要通过明确的命令进行提交或者回滚。
如果只是对某些语句需要进行事务控制,则使用 START TRANSACTION 语句开始一个事务
比较方便,这样事务结束之后可以自动回到自动提交的方式,如果希望所有的事务都不是自
动提交的,那么通过修改 AUTOCOMMIT 来控制事务比较方便,这样不用在每个事务开始的
时候再执行 START TRANSACTION 语句。
如表 14-2 所示的例子演示了使用 START TRANSACTION 开始的事务在提交后自动回到自
动提交的方式;如果在提交的时候使用 COMMIT AND CHAIN,那么会在提交后立即开始一个
新的事务。
表 14-2
START TRANSACTION 和 COMMIT AND CHAIN 的使用例子
session_1
session_2
从表 actor 中查询 actor_id=201 的记录,结果为
空:
mysql> select * from actor where actor_id =
201;
Empty set (0.00 sec)
从表 actor 中查询 actor_id=201 的记录,结果为
空:
mysql> select * from actor where actor_id =
201;
Empty set (0.00 sec)
用 start transaction 命令启动一个事务,往表
actor 中插入一条记录,没有 commit:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into actor
(actor_id,first_name,last_name)
values(201,'Lisa','Tom');
Query OK, 1 row affected (0.00 sec)
查询表 actor,结果仍然为空:
mysql> select * from actor where actor_id =
201;
Empty set (0.00 sec)
执行提交:
mysql> commit;
Query OK, 0 rows affected (0.04 sec)Linux
公社
www.linuxidc.com
173
再次查询表 actor,可以查询到结果:
mysql> select actor_id,last_name from actor
where actor_id in (201,202);
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 201 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
这个事务是按照自动提交执行的:
mysql> insert into actor
(actor_id,first_name,last_name)
values(202,'Lisa','Lan');
Query OK, 1 row affected (0.04 sec)
可以从 actor 表中查询到 session1 刚刚插入的数
据。
mysql> select actor_id,last_name from actor
where actor_id in (201,202);
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 201 | Tom |
| 202 | Lan |
+----------+-----------+
2 rows in set (0.00 sec)
重新用 start transaction 启动一个事务:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
往表 actor 中插入一条记录:
mysql> insert into actor
(actor_id,first_name,last_name)
values(203,'Lisa','TT');
Query OK, 1 row affected (0.00 sec)
用 commit and chain 命令提交:
mysql> commit and chain;
Query OK, 0 rows affected (0.03 sec)
此时自动开始一个新的事务:
mysql> insert into actor
(actor_id,first_name,last_name)
values(204,'Lisa','Mou');
Query OK, 1 row affected (0.00 sec)Linux
公社
www.linuxidc.com
174
session1 刚插入的记录无法看到:
mysql> select actor_id,last_name from actor
where first_name = 'Lisa';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 178 | MONROE T |
| 201 | Tom |
| 202 | Lan |
| 203 | TT |
+----------+-----------+
4 rows in set (0.00 sec)
用 commit 命令提交:
mysql> commit;
Query OK, 0 rows affected (0.06 sec)
session1 插入的新记录可以看到:
mysql> select actor_id,last_name from actor
where first_name = 'Lisa';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 178 | MONROE T |
| 201 | Tom |
| 202 | Lan |
| 203 | TT |
| 204 | Mou |
+----------+-----------+
5 rows in set (0.00 sec)
如果在锁表期间,用 start transaction 命令开始一个新事务,会造成一个隐含的 unlock
tables 被执行,如表 14-3 所示。
表 14-3
start transaction 导致的 unlock tables
session_1
session_2
从表 actor 中查询 actor_id=201 的记录,结果为
空:
mysql> select * from actor where actor_id =
201;
Empty set (0.00 sec)
从表 actor 中查询 actor_id=201 的记录,结果为
空:
mysql> select * from actor where actor_id =
201;
Empty set (0.00 sec)
对表 actor 加写锁:
mysql> lock table actor write;
Query OK, 0 rows affected (0.00 sec)Linux
公社
www.linuxidc.com
175
对表 actor 的读操作被阻塞:
mysql> select actor_id,last_name from actor
where actor_id = 201;
等待
插入一条记录
mysql> insert into actor
(actor_id,first_name,last_name)
values(201,'Lisa','Tom');
Query OK, 1 row affected (0.04 sec)
等待
回滚刚才的记录:
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
等待
用 start transaction 命令重新开始一个事务:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
等待
session1 开始一个事务时,表锁被释放,可以查询:
mysql> select actor_id,last_name from actor
where actor_id = 201;
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 201 | Tom |
+----------+-----------+
1 row in set (17.78 sec)
对 lock 方式加的表锁,不能通过 rollback 进行回
滚。
因此,在同一个事务中,最好不使用不同存储引擎的表,否则 ROLLBACK 时需要对非事
务类型的表进行特别的处理,因为 COMMIT、ROLLBACK 只能对事务类型的表进行提交和回
滚。
通常情况下,只对提交的事务记录到二进制的日志中,但是如果一个事务中包含非事务
类型的表,那么回滚操作也会被记录到二进制日志中,以确保非事务类型表的更新可以被复
制到从(Slave)数据库中。
和 Oracle 的事务管理相同,所有的 DDL 语句是不能回滚的,并且部分的 DDL 语句会造
成隐式的提交。
在事务中可以通过定义 SAVEPOINT,指定回滚事务的一个部分,但是不能指定提交事务
的一个部分。对于复杂的应用,可以定义多个不同的 SAVEPOINT,满足不同的条件时,回滚
不同的 SAVEPOINT。需要注意的是,如果定义了相同名字的 SAVEPOINT,则后面定义的
SAVEPOINT 会覆盖之前的定义。对于不再需要使用的 SAVEPOINT,可以通过 RELEASE
SAVEPOINT 命令删除 SAVEPOINT,删除后的 SAVEPOINT,不能再执行 ROLLBACK TO SAVEPOINT
命令。
如表 14-4 所示的例子就是模拟回滚事务的一个部分,通过定义 SAVEPOINT 来指定需要
回滚的事务的位置。
表 14-4 模拟回滚事务Linux
公社
www.linuxidc.com
176
session_1
session_2
从表 actor 中查询 first_name=’Simon’的记录,
结果为空:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
从表 actor 中查询 first_name=’Simon’的记录,
结果为空:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
启动一个事务,往表 actor 中插入一条记录:
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> insert into actor
(actor_id,first_name,last_name)
values(301,'Simon','Tom');
Query OK, 1 row affected (0.00 sec)
可以查询到刚插入的记录:
mysql> select actor_id,last_name from actor
where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
无法从 actor 表中查到 session1 刚插入的记录:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
定义 savepoint,名称为 test:
mysql> savepoint test;
Query OK, 0 rows affected (0.00 sec)
继续插入一条记录:
mysql> insert into actor
(actor_id,first_name,last_name)
values(302,'Simon','Cof');
Query OK, 1 row affected (0.00 sec)
可以查询到两条记录:
mysql> select actor_id,last_name from actor
where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
| 302 | Cof |
+----------+-----------+
2 rows in set (0.00 sec)
仍然无法查询到结果:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)Linux
公社
www.linuxidc.com
177
回滚到刚才定义的 savepoint:
mysql> rollback to savepoint test;
Query OK, 0 rows affected (0.00 sec)
只能从表 actor 中查询到第一条记录,因为第二条
已经被回滚:
mysql> select actor_id,last_name from actor
where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
仍然无法查询到结果:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
用 commit 命令提交:
mysql> commit;
Query OK, 0 rows affected (0.05 sec)
只能从 actor 表中查询到第一条记录:
mysql> select actor_id,last_name from actor
where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
只能从 actor 表中查询到 session1 插入的第一条
记录:
mysql> select actor_id,last_name from actor
where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
14.3 分布式事务的使用
MySQL 从 5.0.3 开始支持分布式事务,当前分布式事务只支持 InnoDB 存储引擎。一个
分布式事务会涉及多个行动,这些行动本身是事务性的。所有行动都必须一起成功完成,或
者一起被回滚。
14.3.1 分布式事务的原理
在 MySQL 中,使用分布式事务的应用程序涉及一个或多个资源管理器和一个事务管理
器。
·资源管理器(RM)用于提供通向事务资源的途径。数据库服务器是一种资源管理器。
该管理器必须可以提交或回滚由 RM 管理的事务。例如,多台 MySQL 数据库作为多台资源
管理器或者几台 Mysql 服务器和几台 Oracle 服务器作为资源管理器。
·事务管理器(TM)用于协调作为一个分布式事务一部分的事务。TM 与管理每个事务
的 RMs 进行通讯。一个分布式事务中各个单个事务均是分布式事务的“分支事务”。分布式
事务和各分支通过一种命名方法进行标识。Linux
公社
www.linuxidc.com
MySQL 执行 XA MySQL 时,MySQL 服务器相当于一个用于管理分布式事务中的 XA 事务
的资源管理器。与 MySQL 服务器连接的客户端相当于事务管理器。
要执行一个分布式事务,必须知道这个分布式事务涉及到了哪些资源管理器,并且把每
个资源管理器的事务执行到事务可以被提交或回滚时。根据每个资源管理器报告的有关执行
情况的内容,这些分支事务必须作为一个原子性操作全部提交或回滚。要管理一个分布式事
务,必须要考虑任何组件或连接网络可能会故障。
用于执行分布式事务的过程使用两阶段提交,发生时间在由分布式事务的各个分支需要
进行的行动已经被执行之后。
在第一阶段,所有的分支被预备好。即它们被 TM 告知要准备提交。通常,这意味
着用于管理分支的每个 RM 会记录对于被稳定保存的分支的行动。分支指示是否它们可以这
么做。这些结果被用于第二阶段。
在第二阶段,TM 告知 RMs 是否要提交或回滚。如果在预备分支时,所有的分支指
示它们将能够提交,则所有的分支被告知要提交。如果在预备时,有任何分支指示它将不能
提交,则所有分支被告知回滚。
在有些情况下,一个分布式事务可能会使用一阶段提交。例如,当一个事务管理器发现,
一个分布式事务只由一个事务资源组成(即单一分支),则该资源可以被告知同时进行预备
和提交。
14.3.2 分布式事务的语法
分布式事务(XA 事务)的 SQL 语法主要包括:
XA {START|BEGIN} xid [JOIN|RESUME]
XA START xid 用于启动一个带给定 xid 值的 XA 事务。每个 XA 事务必须有一个唯一的 xid
值,因此该值当前不能被其他的 XA 事务使用。
xid 是一个 XA 事务标识符,用来唯一标识一个分布式事务。xid 值由客户端提供,或由
MySQL 服务器生成。xid 值包含 1~3 个部分:
xid: gtrid [, bqual [, formatID ]]
gtrid 是一个分布式事务标识符,相同的分布式事务应该使用相同的 gtrid,这样可以
明确知道 xa 事务属于哪个分布式事务。
bqual 是一个分支限定符,默认值是空串。对于一个分布式事务中的每个分支事务,
bqual 值必须是唯一的。
formatID 是一个数字,用于标识由 gtrid 和 bqual 值使用的格式,默认值是 1。
下面其他 XA 语法中用到的 xid 值,都必须和 START 操作使用的 xid 值相同,也就是表示
对这个启动的 XA 事务进行操作。
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
使事务进入 PREPARE 状态,也就是两阶段提交的第一个提交阶段。
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
这两个命令用来提交或者回滚具体的分支事务。也就是两阶段提交的第二个提交阶段,
分支事务被实际的提交或者回滚。
XA RECOVER
178Linux
公社
www.linuxidc.com
179
XA RECOVER 返回当前数据库中处于 PREPARE 状态的分支事务的详细信息。
分布式的关键在于如何确保分布式事务的完整性,以及在某个分支出现问题时的故障解
决。XA 的相关命令就是提供给应用如何在多个独立的数据库之间进行分布式事务的管理,
包括启动一个分支事务、使事务进入准备阶段以及事务的实际提交回滚操作等,如表 14-5
所示的例子演示了一个简单的分布式事务的执行,事务的内容是在 DB1 中插入一条记录,
同时在 DB2 中更新一条记录,两个操作作为同一事务提交或者回滚。
表 14-5
分布式事务例子
session_1 in DB1
session_2 in DB2
在数据库 DB1 中启动一个分布式事务的一个分支事
务,xid 的 gtrid 为“test”,bqual 为“db1”:
mysql> xa start 'test','db1';
Query OK, 0 rows affected (0.00 sec)
分支事务 1 在表 actor 中插入一条记录:
mysql> insert into actor
(actor_id,first_name,last_name)
values(301,'Simon','Tom');
Query OK, 1 row affected (0.00 sec)
对分支事务 1 进行第一阶段提交,进入 prepare 状态:
mysql> xa end 'test','db1';
Query OK, 0 rows affected (0.00 sec)
mysql> xa prepare 'test','db1';
Query OK, 0 rows affected (0.02 sec)
在数据库 DB2 中启动分布式事务“test”的另外一
个分支事务,xid 的 gtrid 为“test”,bqual 为
“db2”:
mysql> xa start 'test','db2';
Query OK, 0 rows affected (0.00 sec)
分支事务 2 在表 film_actor 中更新了 23 条记录:
mysql> update film_actor set
last_update=now() where actor_id = 178;
Query OK, 23 rows affected (0.04 sec)
Rows matched: 23 Changed: 23 Warnings: 0
对分支事务 2 进行第一阶段提交,进入 prepare 状
态:
mysql> xa end 'test','db2';
Query OK, 0 rows affected (0.00 sec)
mysql> xa prepare 'test','db2';
Query OK, 0 rows affected (0.02 sec)
用 xa recover 命令查看当前分支事务状态:
mysql> xa recover \G
*************************** 1. row
***************************
formatID: 1
gtrid_length: 4
bqual_length: 3
data: testdb1
1 row in set (0.00 sec)
用 xa recover 命令查看当前分支事务状态:
mysql> xa recover \G
*************************** 1. row
***************************
formatID: 1
gtrid_length: 4
bqual_length: 3
data: testdb2
1 row in set (0.00 sec)
两个事务都进入准备提交阶段,如果之前遇到任何错误,都应该回滚所有的分支,以确保分布式事务的正
确。
提交分支事务 1:
mysql> xa commit 'test','db1';
Query OK, 0 rows affected (0.03 sec)
提交分支事务 2:
mysql> xa commit 'test','db2';
Query OK, 0 rows affected (0.03 sec)Linux
公社
www.linuxidc.com
两个事务都到达准备提交阶段后,一旦开始进行提交
操作,就需要确保全部的分支都提交成功。
14.3.3 存在的问题
虽然 MySQL 支持分布式事务,但是在测试过程中,还是发现存在一些问题。
如果分支事务在达到 prepare 状态时,数据库异常重新启动,服务器重新启动以后,可
以继续对分支事务进行提交或者回滚得操作,但是提交的事务没有写 binlog,存在一定的隐
患,可能导致使用 binlog 恢复丢失部分数据。如果存在复制的数据库,则有可能导致主从数
据库的数据不一致。以下演示了这个过程:
(1)从表 actor 中查询 first_name = 'Simon'的记录,有一条。
mysql> select actor_id,last_name from actor where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
(2)启动分布式事务“test”,删除刚才查询的记录。
mysql> xa start 'test';
Query OK, 0 rows affected (0.00 sec)
mysql> delete from actor where actor_id = 301;
Query OK, 1 row affected (0.00 sec)
mysql> select actor_id,last_name from actor where first_name = 'Simon';
Empty set (0.00 sec)
(3)完成第一阶段提交,进入 prepare 状态。
mysql> xa end 'test';
Query OK, 0 rows affected (0.00 sec)
mysql> xa prepare 'test';
Query OK, 0 rows affected (0.03 sec)
(4)此时,数据库异常终止,查询出错。
mysql> select actor_id,last_name from actor where first_name = 'Simon';
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/mnt/db/mysqld.sock'
(2)
ERROR:
180Linux
公社
www.linuxidc.com
Can't connect to the server
(5)启动数据库后,分支事务依然存在。
mysql> xa recover \G
*************************** 1. row ***************************
formatID: 1
gtrid_length: 4
bqual_length: 0
data: test
1 row in set (0.00 sec)
(6)表中记录并没有被删除。
mysql> select actor_id,last_name from actor where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
(7)可以进行提交或者回滚。
mysql> xa commit 'test';
Query OK, 0 rows affected (0.02 sec)
mysql> select actor_id,last_name from actor where first_name = 'Simon';
Empty set (0.00 sec)
在上面测试中,如果重新启动MySQL数据库以后,可以在MySQL的数据库日志中看到分
布式事务的处理情况,数据库启动的时候发现有一个prepare状态的事务,提示需要进行处
理:
InnoDB: Transaction 0 117471044 was in the XA prepared state.
InnoDB: 1 transaction(s) which must be rolled back or cleaned up
InnoDB: in total 0 row operations to undo
InnoDB: Trx id counter is 0 117471488
070710 16:55:41 InnoDB: Started; log sequence number 29 2758352865
070710 16:55:41 InnoDB: Starting recovery for XA transactions...
070710 16:55:41 InnoDB: Transaction 0 117471044 in prepared state after recovery
070710 16:55:41 InnoDB: Transaction contains changes to 1 rows
070710 16:55:41 InnoDB: 1 transactions in prepared state after recovery
070710 16:55:41 [Note] Found 1 prepared transaction(s) in InnoDB
070710 16:55:41 [Warning] Found 1 prepared XA transactions
使用 mysqlbinlog 查看 binlog,可以确认最后提交的这个分支事务并没有记录到 binlog
中,因为复制和灾难恢复都是依赖于binlog的,所以binlog的缺失会导致复制环境的不同步,
181Linux
公社
www.linuxidc.com
182
以及使用 binlog 恢复丢失部分数据。
如果分支事务的客户端连接异常中止,那么数据库会自动回滚未完成的分支事务,如果
此时分支事务已经执行到 prepare 状态,那么这个分布式事务的其他分支可能已经成功提交,
如果这个分支回滚,可能导致分布式事务的不完整,丢失部分分支事务的内容。
表 14-6
客户端连接中止导致分布式事务失败例子
session_1
session_2
从表 actor 中查询 first_name=’Simon’的记录,结
果为空:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
从表 actor 中查询 first_name=’Simon’的记录,
结果为空:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
启动分布式事务 test:
mysql> xa start 'test';
Query OK, 0 rows affected (0.00 sec)
往 actor 表中插入一条记录:
mysql> insert into actor
(actor_id,first_name,last_name)
values(301,'Simon','Tom');
Query OK, 1 row affected (0.00 sec)
事务结束:
mysql> xa end 'test';
Query OK, 0 rows affected (0.00 sec)
查询刚插入的记录,可以显示结果:
mysql> select actor_id,last_name from actor
where first_name = 'Simon';
+----------+-----------+
| actor_id | last_name |
+----------+-----------+
| 301 | Tom |
+----------+-----------+
1 row in set (0.00 sec)
查询刚插入的记录,显示结果为空:
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
完成第一阶段提交,进入 prepare 状态。
mysql> xa prepare 'test';
Query OK, 0 rows affected (0.02 sec)
查询分布式事务“test”的状态:
mysql> xa recover \G
*************************** 1. row
***************************
formatID: 1
gtrid_length: 4
bqual_length: 3Linux
公社
www.linuxidc.com
183
data: test
1 row in set (0.00 sec)
session_1 异常中止
session_1 被回滚。
session1 异常中止后,分布式事务被回滚,
session2 中无法查询到 session1 插入的记录,如
果此时 session2 存在分支事务并且被成功提交,
则会导致分布式事务的不完整。
mysql> select * from actor where first_name =
'Simon';
Empty set (0.00 sec)
如果分支事务在执行到 prepare 状态时,数据库异常,且不能再正常启动,需要使用备
份和 binlog 来恢复数据,那么那些在 prepare 状态的分支事务因为并没有记录到 binlog,所
以不能通过 binlog 进行恢复,在数据库恢复后,将丢失这部分的数据。
总之,MySQL 的分布式事务还存在比较严重的缺陷,在数据库或者应用异常的情况下,
可能会导致分布式事务的不完整。如果应用对于数据的完整性要求不是很高,则可以考虑使
用。如果应用对事务的完整性有比较高的要求,那么对于当前的版本,则不推荐使用分布式
事务。
14.4 小结
事务控制和锁定是 MySQL 的重要特点之一,本章介绍了 MySQL 提供的事务控制和锁定语法,
并对分布式事务进行了简单的介绍。MySQL 中锁的管理涉及的内容很广泛,在后面的优化
篇中我们将会对锁机制、死锁和应用中需要注意的其他问题进行了更深入的讨论。
个人学习笔记,记录日常学习,便于查阅及加深,仅为方便个人使用。