目录
[1、未 commit 事务,客户端崩溃,MySQL将自动回滚](#1、未 commit 事务,客户端崩溃,MySQL将自动回滚)
[2、commit 后,客户端崩溃,插入数据不受影响](#2、commit 后,客户端崩溃,插入数据不受影响)
[4、自动提交事务对单条 SQL 语句的影响](#4、自动提交事务对单条 SQL 语句的影响)
一、什么是事务
(一)概念
MySQL 事务是指数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成,这些操作要么全部执行成功,要么全部不执行,是一个不可分割的工作单位。事务主要用于保证数据的一致性和完整性,特别是在需要多个操作同时成功或同时失败的场景中,比如银行转账、订单处理等。
(二)事务的四大属性
事务的特性:
- 原子性:事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个状态;
- 一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态;
- 隔离性:多个事务并发执行时,一个事务的执行不会影响其他事务;
- 持久性:一旦事务提交,其结果就是永久性的,即使系统崩溃也不会丢失。
(三)事务的作用
事务的出现是为了简化程序开发时可能需要考虑的多种细节问题。例如:当银行账户 A 向银行账户 B 发起转账,首先需要现在 A 中扣除目标金额后再向B中添加目标金额,假如在扣除A账户的金额后出现了网络异常导致转账失败,这时的正常情况应该是账户A上返回了目标金额,事务的出现就使得该操作可以由 MySQL 自动完成而不需要程序员特殊处理。
(四)事务的提交方式
事务的提交方式分为自动提交和手动提交。在先前学习MySQL语句时并没有对事务进行过特殊操作,这是因为 MySQL 默认设置自动提交。也就是每当执行一条语句后 MySQL自动将该语句进行事务的提交。
查看事务自动提交方式:
sql
//MySQl 默认打开自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.02 sec)
//设置自动提交事务关闭
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
二、事务的启动、回滚与提交
为方便演示,需关闭事务自动提交、将事务的隔离级别设为最低并准备一个测试表:
sql
//打开事务自动提交
mysql> set autocommit=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
//设置隔离级别(需重启终端)
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
//创建测试表
mysql> create table test(
-> id int primary key,
-> name varchar(20) not null,
-> salary decimal(10,2) default 0.0
-> );
Query OK, 0 rows affected (0.02 sec)
(一)事务的启动、回滚与提交
sql
//手动开启事务 (手动开始事务后该事务不受自动提交影响)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
//保存点1
mysql> savepoint save1;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(1,'张三',5000);
Query OK, 1 row affected (0.00 sec)
//保存点2
mysql> savepoint save2;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(2,'李四',4500);
Query OK, 1 row affected (0.00 sec)
//此时有两条记录
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)
//回滚至保存点2
mysql> rollback to save2;
Query OK, 0 rows affected (0.00 sec)
//此时变为一条记录
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
+----+--------+---------+
1 row in set (0.00 sec)
//回滚至最开始
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
//无记录
mysql> select * from test;
Empty set (0.00 sec)
由上述结果可以看出,手动开启事务后将不受自动提交的影响。以上便是手动开启事务以及回滚操作。
(二)特殊情况
以下情况都是最低隔离级别下的操作(读未提交),为方便说明开启两个终端进行演示:
1、未 commit 事务,客户端崩溃,MySQL将自动回滚
sql
//客户端A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(1, '张三', 5000);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(2, '李四', 4500);
Query OK, 1 row affected (0.00 sec)
mysql> Aborted
[X@centos-414 ~]$
//客户端B
mysql> select * from test;
Empty set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
+----+--------+---------+
1 row in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from test;
Empty set (0.01 sec)

2、commit 后,客户端崩溃,插入数据不受影响
sql
//客户端A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(1, '张三', 5000);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(2, '李四', 4500);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> Aborted
//客户端B
mysql> select * from test;
Empty set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
+----+--------+---------+
1 row in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

3、手动开启事务不受自动提交事务影响
sql
//客户端A
mysql> set autocommit=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(3, '王五', 5500);
Query OK, 1 row affected (0.00 sec)
mysql> Aborted
//客户端B
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
| 3 | 王五 | 5500.00 |
+----+--------+---------+
3 rows in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.01 sec)
4、自动提交事务对单条 SQL 语句的影响
sql
//客户端A
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
mysql> insert into test values(3, '王五', 5500);
Query OK, 1 row affected (0.00 sec)
mysql> Aborted
//客户端B
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
| 3 | 王五 | 5500.00 |
+----+--------+---------+
3 rows in set (0.00 sec)
mysql> select * from test;
+----+--------+---------+
| id | name | salary |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 4500.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

5、结论
- 只要输入 begin 或者 start transaction,事务就必须通过 commit 提交才会持久化,与是否设置自动提交无关;
- 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC );
- 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)
- 如果一个事务被提交了则无法回退;
- InnoDB 支持事务, MyISAM 不支持事务
三、事务的隔离级别
(一)什么是隔离性
事务的隔离性是数据库事务的四大特性之一,它确保并发执行的多个事务相互独立,一个事务的操作不会被其他事务干扰,从而避免数据不一致问题。隔离性通过不同的隔离级别来控制事务之间的可见性和影响程度。
查看隔离级别:
sql
//查看全局隔离级别(一般默认为REPEATABLE-READ)
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.01 sec)
//查看此次会话隔离级别(一般开启MySQL客户端后该值由全局隔离级别进行初始化)
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
//查看此次会话隔离级别
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
在使用MySQL客户端时的隔离级别由会话隔离级别等级决定。
设置隔离级别:
sql
//设置会话隔离级别
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
(二)事务的隔离级别
SQL标准定义了四种隔离级别:
- 读未提交
允许事务读取其他事务未提交的数据,可能出现脏读、不可重复读和幻读的情况;- 读已提交
只允许读取已提交的数据,避免脏读,但存在不可重复读和幻读的情况;- 可重复读
确保同一事务多次读取同一数据结果的一致性,可能会出现幻读的情况;- 串行化
最高隔离的级别,事务完全串行化,可避免所有并发问题,但性能低。
(三)四种隔离级别详解
1、读未提交
在多个并行的会话中启动事务,一个事务在改动数据库哪怕没有commit提交,其他事务也是能够实时的看到它修改的数据。一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种不合理的现象叫做脏读(dirty read)。

上图可以看出,即使左端事务没有进行提交操作,但右端事务仍可以看到表的操作,这就是脏读。
2、读已提交
事务A在commit提交事务之前,所做的修改是不会被其他事务看到的,一旦事务A发起commit之后,其他事务就能看到事务A对数据的修改。这就造成了其他事务在不同的时间点select查看数据库时,会查到不同的数据。这种现象叫不可重复读。

上图可知,只要事务的操作被提交,那么其他事务可以查看到该事务的插入操作,这就会导致其他事务对同一表的查询结果可能会发生变化,这就是不可重复读。
3、可重复读
可重复读是MySQL默认的隔离级别。

上图可知,即使事务的操作被提交,其他事务仍然无法查看到该事务对表的操作,只要其他事务也提交以后才能查看到其他事务对表的操作。
4、串行化
串行化就是对所有事务进行加锁,事务的执行(一般对查询操作不进行加锁)全部挨个排队,这就导致了效率低下问题。

开启事务A和事务B,两个事务同时select读取将使用共享锁,不会串行化;事务A中有更新等操作,会阻塞A,直到事务B提交。如果事务A阻塞时间过长,将会由于锁等待超时退出当前事务。
