暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

开发篇-MySQL事务控制和锁定语句

DBA天团 2021-02-05
484

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}] ...

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_1

session_2

当前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}


默认情况下,MySQL是自动提交(Autocommit)的,如果需要通过明确的Commit和Rollback来提交和回滚事务,那么需要通过明确的事务控制命令来开始事务,这是和Oracle的事务管理明显不同的地方。如果应用是从Oracle数据库迁移到MySQL数据库,则需要确保应用中是否对事务进行了明确的管理。

   START TRANSACTION或BEGIN语句可以开始一项新的事务。

   COMMIT和ROLLBACK用来提交或者回滚事务。

   CHAIN和RELEASE子句分别用来定义在事务提交或者回滚之后的操作,CHAIN会立即启动一个新事物,并且和刚才的事务具有相同的隔离级别,RELEASE则会断开和客户端的连接。

   SET AUTOCOMMIT可以修改当前连接的提交方式,如果设置了SET AUTOC- OMMIT=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)



再次查询表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)

续表

session_1

session_2

这个事务是按照自动提交执行的:

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)



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)

续表

session_1

session_2

用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)



对表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)

等待

续表

session_1

session_2

用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               模拟回滚事务

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)


续表

session_1

session_2

可以查询到刚插入的记录:

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)

回滚到刚才定义的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)

续表

session_1

session_2

用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进行通信。一个分布式事务中各个单个事务均是分布式事务的“分支事务”。分布式事务和各分支通过一种命名方法进行标识。

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

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)


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:

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的缺失会导致复制环境的不同步,以及使用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: 3

          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中锁的管理涉及的内容很广泛,在后面的优化篇中我们将会对锁机制、死锁和应用中需要注意的其他问题进行了更深入的讨论。



欢迎分享


网易乐得DBA组负责网易乐得电商、网易邮箱、网易技术部数据库日常运维,负责数据库私有云平台的开发和维护,负责数据库及数据库中间件的开发和测试等,分享最前沿实用数据库干货,关注网易乐得DBA,精深数据库神功。网易乐得长期招聘,点击阅读原文投递简历


文章转载自DBA天团,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论