MySQL中SET TRANSACTION会不会影响事务

45次阅读
没有评论

共计 15775 个字符,预计需要花费 40 分钟才能阅读完成。

这篇文章给大家介绍 MySQL 中 SET TRANSACTION 会不会影响事务,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

MySQL 支持 SQL:1992 标准中的所有事务隔离级别,使用 SET TRANSACTION 来设置不同的事务隔离级别或访问模式。

我们都知道,MySQL 的内置引擎中只有 InnoDB、NDB 支持事务,而又以 InnoDB 引擎对于事务的支持最全面也使用最广泛,所以本文的讨论都是基于 InnoDB 引擎,实验中用的表都是基于 InnoDB 的表。

FeatureMyISAMMemoryInnoDBArchiveNDBTransactionsNoNoYesNoYes

MySQL 中可以使用 SET TRANSACTION 来影响事务特性,此语句可以指定一个或多个由逗号分隔的特征值列表,每个特征值设置事务隔离级别或访问模式。此语句在 MySQL 5.7 中的完整语法

SET [GLOBAL | SESSION] TRANSACTION
 transaction_characteristic [, transaction_characteristic] ...
transaction_characteristic: { ISOLATION LEVEL level
 | access_mode
}level: {
 REPEATABLE READ
 | READ COMMITTED
 | READ UNCOMMITTED
 | SERIALIZABLE}
access_mode: { READ WRITE
 | READ ONLY}

语法很简单清晰,这里有几个关键概念需要理解清楚。

Transaction Isolation Levels(事务隔离级别)

事务隔离是数据库的基础能力,ACID 中的 I 指的就是事务隔离,通俗点讲就是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

那么到底如何做才算是相互隔离呢?SQL:1992 标准规定了四种事务隔离级别:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

InnoDB 对四种隔离级别都支持,默认级别是 REPEATABLE READ。

root@database-one 07:43: [(none)]  select @@tx_isolation;
+-----------------+| @@tx_isolation |
+-----------------+| REPEATABLE-READ |
+-----------------+1 row in set (0.00 sec)

新建会话进行验证,会话的默认隔离级别确实 REPEATABLE-READ。

InnoDB 是靠不同的锁策略实现每个事务隔离级别,隔离级别越高付出的锁成本也就会越高。我们通过例子来看看不同级别的区别。

root@database-one 08:38: [gftest]  create table testtx(name varchar(10),money decimal(10,2)) engine=innodb;
Query OK, 0 rows affected (0.12 sec)
root@database-one 08:42: [gftest]  insert into testtx values(A ,6000),(B ,8000),(C ,9000);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
root@database-one 08:43: [gftest]  select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

上面创建了表 testtx,并插入了 3 条数据,表示 A 有 6000 元,B 有 8000 元,C 有 9000 元。

REPEATABLE READ,同一事务内的 consistent reads 读取由第一次读取建立的快照。这意味着,如果在同一事务中发出多个普通(非锁定)SELECT 语句,则这些 SELECT 语句查到的数据保持一致。

创建会话 1,关闭 MySQL 默认的事务自动提交模式(相关知识可以参考
MySQL 中的事务控制语句)。

root@database-one 08:58: [(none)]  prompt \u@database-one \R:\m:\s [\d] session1 
PROMPT set to  \u@database-one \R:\m:\s [\d] session1 root@database-one 08:58:41 [(none)] session1 use gftest;
Database changed
root@database-one 08:58:55 [gftest] session1 SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
root@database-one 08:59:21 [gftest] session1 show variables like  autocommit 
+---------------+-------+| Variable_name | Value |
+---------------+-------+| autocommit | OFF |
+---------------+-------+1 row in set (0.02 sec)
root@database-one 08:59:36 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

创建会话 2,关闭 MySQL 默认的事务自动提交模式(相关知识可以参考
MySQL 中的事务控制语句)。

root@database-one 09:01: [(none)]  prompt \u@database-one \R:\m:\s [\d] session2 
PROMPT set to  \u@database-one \R:\m:\s [\d] session2 root@database-one 09:02:13 [(none)] session2 use gftest;
Database changed
root@database-one 09:02:24 [gftest] session2 SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:02:30 [gftest] session2 show variables like  autocommit 
+---------------+-------+| Variable_name | Value |
+---------------+-------+| autocommit | OFF |
+---------------+-------+1 row in set (0.00 sec)
root@database-one 09:02:37 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

创建会话 3,关闭 MySQL 默认的事务自动提交模式(相关知识可以参考
MySQL 中的事务控制语句)。

root@database-one 09:03: [(none)]  prompt \u@database-one \R:\m:\s [\d] session3 
PROMPT set to  \u@database-one \R:\m:\s [\d] session3 root@database-one 09:03:44 [(none)] session3 use gftest;
Database changed
root@database-one 09:03:47 [gftest] session3 SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:03:56 [gftest] session3 show variables like  autocommit 
+---------------+-------+| Variable_name | Value |
+---------------+-------+| autocommit | OFF |
+---------------+-------+1 row in set (0.01 sec)
root@database-one 09:04:04 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

A 给 B 转 100 元。在 session1 中模拟。

root@database-one 09:06:03 [gftest] session1 update testtx set money=money-100 where name= A 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 09:07:34 [gftest] session1 update testtx set money=money+100 where name= B 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 09:07:58 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session1 看到了金额进行了变化,但还未进行提交。

此时,分别去 session2、session3 进行查询。

root@database-one 09:02:45 [gftest] session2 
root@database-one 09:12:23 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:04:10 [gftest] session3 
root@database-one 09:14:12 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session2、session3 均未看到金额变化。

A 对转账进行确认,即提交。

root@database-one 09:09:28 [gftest] session1 commit;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:18:03 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

此时,再分别去 session2、session3 进行查询。

root@database-one 09:12:28 [gftest] session2 
root@database-one 09:18:15 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:14:22 [gftest] session3 
root@database-one 09:18:24 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session2、session3 还未看到金额变化。因为他们还在自己的事务中(由自己 session 第一个 select * from testtx 即隐式开启了事务),根据 REPEATABLE READ 事务隔离的原则确实不应该看到。

当 session2、session3 结束当前事务后,再去查询就能看到变化了。

root@database-one 09:18:20 [gftest] session2 
root@database-one 09:26:58 [gftest] session2 commit;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:27:05 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:18:26 [gftest] session3 
root@database-one 09:27:17 [gftest] session3 rollback;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:27:24 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

READ COMMITTED,即使在同一事务中,每个 consistent read 操作都设置并读取自己的新快照。

我们将数据还原,并调整三个会话的事务隔离级别均为 READ COMMITTED。

root@database-one 09:38:42 [gftest] session1 update testtx set money=6000 where name= A 
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
root@database-one 09:39:20 [gftest] session1 update testtx set money=8000 where name= B 
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 09:39:44 [gftest] session1 commit;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:39:49 [gftest] session1 SET SESSION TRANSACTION ISOLATION LEVEL read committed;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:40:33 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:41:31 [gftest] session2 SET SESSION TRANSACTION ISOLATION LEVEL read committed;
Query OK, 0 rows affected (0.00 sec)
root@database-one 09:41:44 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:42:16 [gftest] session3 SET SESSION TRANSACTION ISOLATION LEVEL read committed;
Query OK, 0 rows affected (0.01 sec)
root@database-one 09:42:24 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

A 给 B 转 100 元。在 session1 中模拟。

root@database-one 09:40:42 [gftest] session1 update testtx set money=money-100 where name= A 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 09:44:10 [gftest] session1 update testtx set money=money+100 where name= B 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 09:44:20 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session1 看到了金额进行了变化,但还未进行提交。

此时,分别去 session2、session3 进行查询。

root@database-one 09:42:28 [gftest] session3 
root@database-one 09:47:15 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:42:28 [gftest] session3 
root@database-one 09:47:15 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session2、session3 均未看到金额变化。

A 对转账进行确认,即提交。

root@database-one 09:50:37 [gftest] session1 commit;
Query OK, 0 rows affected (0.03 sec)
root@database-one 09:50:43 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

此时,再分别去 session2、session3 视角进行查询。

root@database-one 09:48:02 [gftest] session2 
root@database-one 09:52:18 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 09:48:18 [gftest] session3 
root@database-one 09:53:11 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session2、session3 均看到金额变化。因为他们虽然还在自己的事务中(由自己 session 第一个 select * from testtx 即隐式开启了事务),根据 READ COMMITTED 事务隔离的原则应该看到。

READ UNCOMMITTED,SELECT 语句是以非锁定方式执行的,但可能会使用数据的早期版本,这样的读取是不一致的,因此也被称为脏读。

我们将数据还原,并调整三个会话的事务隔离级别均为 READ COMMITTED。

root@database-one 10:02:49 [gftest] session1 update testtx set money=6000 where name= A 
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 10:03:10 [gftest] session1 update testtx set money=8000 where name= B 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 10:03:20 [gftest] session1 commit;
Query OK, 0 rows affected (0.00 sec)
root@database-one 10:03:30 [gftest] session1 SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted;
Query OK, 0 rows affected (0.00 sec)
root@database-one 10:03:49 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 10:02:52 [gftest] session2 SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted;
Query OK, 0 rows affected (0.00 sec)
root@database-one 10:04:58 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 10:05:35 [gftest] session3 SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted;
Query OK, 0 rows affected (0.00 sec)
root@database-one 10:05:37 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

A 给 B 转 100 元。在 session1 中模拟。

root@database-one 10:06:43 [gftest] session1 update testtx set money=money-100 where name= A 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 10:06:47 [gftest] session1 update testtx set money=money+100 where name= B 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@database-one 10:06:57 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session1 看到了金额进行了变化,但还未进行提交。

此时,分别去 session2、session3 进行查询。

root@database-one 10:05:07 [gftest] session2 
root@database-one 10:08:34 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 10:06:02 [gftest] session3 
root@database-one 10:08:42 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session2 看到金额变化,session3 未看到金额变化。因为他们虽然还在自己的事务中(由自己 session 第一个 select * from testtx 即隐式开启了事务),根据 READ UNCOMMITTED 事务隔离的原则,session3 没有看到金额变化是因为使用了数据的早期版本。这里需要特别注意,有时可能是 session2 会看到金额变化、有时可能是 session3 会看到金额变化、有时可能是 session2 和 session3 都会看到金额变化、有时可能是 session2 和 session3 都不会看到金额变化,这个是由 MySQL 根据数据的版本情况即时确定的。

A 对转账进行确认,即提交。

root@database-one 10:35:52 [gftest] session1 commit;
Query OK, 0 rows affected (0.01 sec)
root@database-one 10:36:01 [gftest] session1 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

此时,再分别去 session2、session3 视角进行查询。

root@database-one 10:09:24 [gftest] session2 
root@database-one 11:09:45 [gftest] session2 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 11:08:29 [gftest] session3 
root@database-one 11:11:54 [gftest] session3 select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 5900.00 |
| B | 8100.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)

session2、session3 均看到金额变化。

SERIALIZABLE,这个级别类似于 REPEATABLE READ,但更严格。在非自动提交模式下,InnoDB 隐式地将所有 SELECT 语句转换为 SELECT … LOCK IN SHARE MODE。在自动提交模式下,SELECT 在自己的事务里,以事务的原则运行。

因为效果和 REPEATABLE READ 类似,我这里就不再演示了,有兴趣的同学可以自己验证。SERIALIZABLE 执行的规则比 REPEATABLE READ 更为严格,主要用于特殊情况,如 XA 事务、解决并发和死锁问题等场景。

Transaction Access Mode(事务访问模式)

事务的访问模式很容易理解,就是指在事务中如何对表中的数据进行使用,分为 READ WRITE 和 READ ONLY,默认是 READ WRITE。

还是 testtx 这张表,我们开启一个 READ ONLY 事务,对其中的数据进行修改,看看会发生什么。

root@database-one 11:56: [gftest]  select @@tx_isolation,@@autocommit;
+-----------------+--------------+| @@tx_isolation | @@autocommit |
+-----------------+--------------+| REPEATABLE-READ | 1 |
+-----------------+--------------+1 row in set (0.00 sec)
root@database-one 11:57: [gftest]  SET SESSION TRANSACTION read only;
Query OK, 0 rows affected (0.00 sec)
root@database-one 11:57: [gftest]  start transaction;
Query OK, 0 rows affected (0.00 sec)
root@database-one 11:59: [gftest]  select * from testtx;
+------+---------+| name | money |
+------+---------+| A | 6000.00 |
| B | 8000.00 |
| C | 9000.00 |
+------+---------+3 rows in set (0.00 sec)
root@database-one 11:59: [gftest]  update testtx set money=0 where name= A 
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.

可以看到,READ ONLY 模式的事务修改数据时会报错。

Transaction Characteristic Scope(事务属性的作用范围)

细心的同学可能已经注意到,在 SET TRANSACTION 时有可选关键字 GLOBAL 和 SESSION,它们决定了事务属性的作用范围。

使用 GLOBAL 时,该语句影响所有后续会话,现有会话不受影响。

使用 SESSION 时,该语句影响当前会话中的所有后续事务。

不使用 GLOBAL 或 SESSION 时,该语句仅影响会话中执行的下一个事务。

关于 MySQL 中 SET TRANSACTION 会不会影响事务就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-28发表,共计15775字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)