【MySQL】事务及隔离性
目录
一、什么是事务
(一)概念
(二)事务的四大属性
(三)事务的作用
(四)事务的提交方式
二、事务的启动、回滚与提交
(一)事务的启动、回滚与提交
(二)特殊情况
1、未 commit 事务,客户端崩溃,MySQL将自动回滚
2、commit 后,客户端崩溃,插入数据不受影响
3、手动开启事务不受自动提交事务影响
4、自动提交事务对单条 SQL 语句的影响
5、结论
三、事务的隔离级别
(二)事务的隔离级别
(三)四种隔离级别详解
1、读未提交
2、读已提交
3、可重复读
4、串行化
一、什么是事务
(一)概念
MySQL 事务是指数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成,这些操作要么全部执行成功,要么全部不执行,是一个不可分割的工作单位。事务主要用于保证数据的一致性和完整性,特别是在需要多个操作同时成功或同时失败的场景中,比如银行转账、订单处理等。
(二)事务的四大属性
事务的特性:
- 原子性:事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个状态;
- 一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态;
- 隔离性:多个事务并发执行时,一个事务的执行不会影响其他事务;
- 持久性:一旦事务提交,其结果就是永久性的,即使系统崩溃也不会丢失。
(三)事务的作用
事务的出现是为了简化程序开发时可能需要考虑的多种细节问题。例如:当银行账户 A 向银行账户 B 发起转账,首先需要现在 A 中扣除目标金额后再向B中添加目标金额,假如在扣除A账户的金额后出现了网络异常导致转账失败,这时的正常情况应该是账户A上返回了目标金额,事务的出现就使得该操作可以由 MySQL 自动完成而不需要程序员特殊处理。
(四)事务的提交方式
事务的提交方式分为自动提交和手动提交。在先前学习MySQL语句时并没有对事务进行过特殊操作,这是因为 MySQL 默认设置自动提交。也就是每当执行一条语句后 MySQL自动将该语句进行事务的提交。
查看事务自动提交方式:
//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)
二、事务的启动、回滚与提交
为方便演示,需关闭事务自动提交、将事务的隔离级别设为最低并准备一个测试表:
//打开事务自动提交
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)
(一)事务的启动、回滚与提交
//手动开启事务 (手动开始事务后该事务不受自动提交影响)
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将自动回滚
//客户端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 后,客户端崩溃,插入数据不受影响
//客户端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、手动开启事务不受自动提交事务影响
//客户端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 语句的影响
//客户端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 不支持事务
三、事务的隔离级别
(一)什么是隔离性
事务的隔离性是数据库事务的四大特性之一,它确保并发执行的多个事务相互独立,一个事务的操作不会被其他事务干扰,从而避免数据不一致问题。隔离性通过不同的隔离级别来控制事务之间的可见性和影响程度。
查看隔离级别:
//查看全局隔离级别(一般默认为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客户端时的隔离级别由会话隔离级别等级决定。
设置隔离级别:
//设置会话隔离级别
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阻塞时间过长,将会由于锁等待超时退出当前事务。