MySQL事务控制流与ACID特性

 更新时间:2021年08月24日 14:01:25   作者:Wallace JW  
本文将会介绍 MySQL 的事务 ACID 特性和 MySQL 事务控制流程的语法,并介绍事务并发处理中可能出现的异常情况,比如脏读、幻读、不可重复读等等,最后介绍事务隔离级别。感兴的小伙伴可以一起来学习

一、ACID 特性

事务处理是一种对必须整批执行的 MySQL 操作的管理机制,在事务过程中,除非整批操作全部正确执行,否则中间的任何一个操作出错,都会回滚 (Rollback) 到最初的安全状态以确保不会对系统数据造成错误的改动。

MySQL 5.5 之后,默认的存储引擎从 MyLSAM 替换成了 InnoDB,这其中的一个重要原因就是因为 InnoDB 支持事务,我们用 SHOW ENGINES 来看一下 MySQL 中对各种存储引擎的描述。

事务最重要的四个特性通常被称为 ACID 特性:
A - Atomicity 原子性: 一个事务是一个不可分割的最小单位,事务中的所有操作要么全部成功,要么全部失败,没有中间状态。原子性主要是通过事务日志中的回滚日志(undo log)来实现的,当事务对数据库进行修改时,InnoDB 会根据操作生成相反操作的 undo log,比如说对 insert 操作,会生成 delete 记录,如果事务执行失败或者调用了 rollback,就会根据 undo log 的内容恢复到执行之前的状态。

C - Consistency 一致性: 事务执行之前和执行之后数据都是合法的一致性状态,即使发生了异常,也不会因为异常引而破坏数据库的完整性约束,比如唯一性约束等。

I - Isolation 隔离性: 每个事务是彼此独立的,不会受到其他事务的执行影响,事务在提交之前对其他事务不可见。隔离性通过事务的隔离级别来定义,并用锁机制来保证写操作的隔离性,用 MVCC 来保证读操作的隔离性,将在下文详细介绍。

D - Durability 持久性: 事务提交之后对数据的修改是持久性的,即使数据库宕机也不会丢失,通过事务日志中的重做日志(redo log)来保证。事务修改之前,会先把变更信息预写到 redo log 中,如果数据库宕机,恢复后会读取 redo log 中的记录来恢复数据。

二、事务控制语法

MySQL 事务控制有几个重要节点,分别是事务的开启,提交,回滚和保存点。

开启事务代表事务开始执行,语句为 START TRANSACTION 或者 BEGIN,提交事务代表将事务中的所有更新都写到磁盘的物理数据库,事务正常执行结束,语句为 COMMIT,如果发生异常需要回滚,语句为 ROLLBACK。要注意的是,一旦事务已经提交,就不能回滚了,因此,在代码执行过程中捕获到异常的时候需要直接执行 rollback 而不是 commit。

比如 A 向 B 转账 100 元的事务:

// 正常执行,提交
BEGIN; # 开启事务
UPDATE account_balance SET balance = balance - 100.00 WHERE account_name = 'A';
UPDATE account_balance SET balance = balance + 100.00 WHERE account_name = 'B';
COMMIT; # 提交事务

// 发生异常,回滚
BEGIN; # 开启事务
UPDATE account_balance SET balance = balance - 100.00 WHERE account_name = 'A';
UPDATE account_balance SET balance = balance + 100.00 WHERE account_name = 'B';
ROLLBACK; # 事务回滚

在复杂场景中,有时我们不需要全盘回滚整个操作,而是分批执行,回滚到某个节点就好了,相当于是在一个大事务下嵌套了若干个子事务,在 MySQL 中可以使用保留点 SAVEPOINT 来实现。

BEGIN;
insert into user_tbl (id) values (1) ;
insert into user_tbl (id) values (2) ;
ROLLBACK;   # 1,2 都没有写入

BEGIN;
insert into user_tbl (id) values (1) ;
SAVEPOINT s1;
insert into user_tbl (id) values (2) ;
ROLLBACK TO s1;   # 回滚到保留点 s1, 因此 1 成功写入,2 被回滚, 最终结果为 1

RELEASE SAVEPOINT s1; # 释放保留点

顺便提一下,事务有隐式事务(自动提交)和显示事务(必须手动提交)两种,MySQL 默认为隐式事务,会进行自动提交,通过 autocommit 参数来控制。

# 查看变量
SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
# 开启自动提交(默认)
SET autocommit = 1;
# 关闭自动提交
SET autocommit = 0;

在自动提交状态下,如果没有显示的开启事务,那每一条 DML 语句都是一个事务,系统会自动对每一条 sql 执行 commit 操作。使用 BEGIN 或 START TRANSACTION 开启一个事务之后,自动提交将保持禁用状态,直到使用 COMMIT 或 ROLLBACK 结束事务之后,自动提交模式会恢复到之前的状态。

关于事务还有另一个参数 completion_type,默认取值为 0 (NO_CHAIN)

# 查看变量
SHOW VARIABLES LIKE 'completion_type';
+-----------------+----------+
| Variable_name   |   Value  |
+-----------------+----------+
| completion_type | NO_CHAIN |
+-----------------+----------+

completion_type = 0: 默认值,执行 commit 后不会自动开启新的事务。
completion_type = 1: 执行 commit 时,相当于执行 COMMIT AND CHAIN,自动开启一个相同隔离级别的事务。
completion_type = 2: 执行 commit 时,相当于执行 COMMIT AND RELEASE,提交事务后自动断开服务器连接。

三、事务并发异常

在实际产线环境下,可能会存在大规模并发请求的情况,如果没有妥善的设置事务的隔离级别,就可能导致一些异常情况的出现,最常见的几种异常为脏读(Dirty Read)、幻读(Phantom Read)不可重复读(Unrepeatable Read)。

1、脏读

脏读指一个事务访问到了另一个事务未提交的数据,如下过程:

  • 假设 a 的值为 1,事务 2 把 a 改为 2,此时事务还未提交
  • 在这个时候,事务 1 读取 a,读得 a 的值为 2,事务 1 读取完成
  • 结果事务 2 回滚了对 a 的修改(或者是未 commit),于是 a 的值变回 1
  • 这就导致事实上 a 的值为 1,但是事务 1 取得的结果为 2,所以事务 1 读到了脏数据,发生脏读

2、不可重复读

不可重复读指一个事务多次读取同一数据的过程中,数据值 内容 发生了改变,导致没有办法读到相同的值,描述的是针对同一条数据 update/delete 的现象,如下过程:

  • 事务 1 读取 a,此时 a = 1
  • 此时事务 2 将 a 修改为 2,并成功提交,改动生效
  • 事务 1 又一次读取 a,此时 a = 2
  • 事务 1 在同一个事务里面两次读取同一个值,数据值内容却发生了改变,发生不可重复读

3、幻读

幻读指一个事务多次读取同一数据的过程中,数据 条数 发生了改变,仿佛产生了幻觉,描述的是针对全表 insert/delete 的现象,如下过程:

  • 事务 1 第一次读取数量,得到 10 条数据
  • 此时事务 2 插入了一条数据并成功提交,改动生效,数据变成 11 条
  • 事务 1 再次读取数量,得到 11 条数据,对事务 1 而言莫名其妙的多了一条,好像产生幻觉了一样,发生幻读

四、事务隔离级别

串行化的事务处理方式当然是最安全的,但是串行无法满足数据库高并发访问的需求,作为妥协,有时不得不降低数据库的隔离标准来换取事务的并发能力,通过在可控的范围内牺牲正确性来换取效率的提升,这种权衡通过事务的隔离级别来实现。

数据库有 4 种事务隔离级别,由低到高依次为 读未提交 Read Uncommitted 读已提交 Read Committed 可重复读 Repeatable Read串行化 Serializable

(1)读未提交 Read Uncommitted
允许读取未提交的内容,这种级别下的查询不会加锁,因此脏读、不可重复读、幻读都有可能发生。
(2)读已提交 Read Committed
只允许读取已提交的内容,这种级别下的查询不会发生脏读,因为脏数据属于未提交的数据,所以不会被读取,但是依然有可能发生不可重复读和幻读。
(3)可重复读 Repeatable Read (MySQL 的默认隔离级别)
使用行级锁来保证一个事务在相同查询条件下两次查询得到的数据结果一致,可以避免脏读和不可重复读,但是没有办法避免幻读。
(4)串行化 Serializable
使用表级锁来保证所有事务的串行化,可以防止所有的异常情况,但是牺牲了系统的并发性。

四种隔离级别中上述三种异常情况的容忍度如下(代表允许,代表禁止):

查看隔离级别的命令为:

SHOW VARIABLES LIKE 'transaction_isolation';
# 或者
SELECT @@global.tx_isolation, @@tx_isolation;

第二种方式可以查看全局和当前会话的隔离级别。

设置隔离级别的命令为:

# 将当前会话的隔离级别设为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 
# 将全局的隔离级别设为读未提交
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 

到此这篇关于MySQL 事务详解的文章就介绍到这了,更多相关MySQL内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅谈MySQL数据库表锁了怎么解锁

    浅谈MySQL数据库表锁了怎么解锁

    在使用 MySQL 数据库时,有时候会发生某个表被锁住的情况,这可能会导致其他用户无法对该表进行读写操作,影响系统的正常运行,本文主要介绍了浅谈MySQL数据库表锁了怎么解锁,感兴趣的可以了解一下
    2023-10-10
  • 详解MySQL中的存取权限

    详解MySQL中的存取权限

    这篇文章主要介绍了详解MySQL中的存取权限,针对用户使用数据库权限分配的问题做出说明,需要的朋友可以参考下
    2015-07-07
  • MySQL自增ID耗尽实例讲解

    MySQL自增ID耗尽实例讲解

    这篇文章主要给大家介绍了关于MySQL自增ID耗尽的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Mysql具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • 简单介绍MySQL中GROUP BY子句的使用

    简单介绍MySQL中GROUP BY子句的使用

    这篇文章主要介绍了简单介绍MySQL中GROUP BY子句的使用,是MySQL入门学习中的基础知识,需要的朋友可以参考下
    2015-05-05
  • 浅谈MySQL表空间回收的正确姿势

    浅谈MySQL表空间回收的正确姿势

    随着时间的推移,表里面的数据越来越多,表数据文件越来越大,数据库占用的空间自然也逐渐增长,本文主要介绍了MySQL表空间回收,感兴趣的可以了解一下
    2021-09-09
  • Mysql help命令(帮助信息)中文注解

    Mysql help命令(帮助信息)中文注解

    这篇文章主要介绍了Mysql help命令(帮助信息)中文注解,英文不好的同学可以收藏起来了,非常好的一篇中文翻译文章,需要的朋友可以参考下
    2014-09-09
  • MySQL 加锁控制并发的方法

    MySQL 加锁控制并发的方法

    这篇文章主要介绍了MySQL 加锁控制并发的方法,帮助大家更好的理解和使用MySQL,感兴趣的朋友可以了解下
    2021-01-01
  • MySQL数据库的触发器的使用

    MySQL数据库的触发器的使用

    这篇文章主要介绍了MySQL数据库的触发器的使用,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-09-09
  • MySQL数据库 触发器 trigger

    MySQL数据库 触发器 trigger

    这篇文章主要介绍了MySQL数据库 触发器 trigger,触发器是一种特殊类型的存储过程,触发器通过事件进行触发而被执行,可通过数据库中的相关表实现级联更改,保证数据安全,进行安全校验
    2022-06-06
  • MySQL使用distinct去掉查询结果重复的问题

    MySQL使用distinct去掉查询结果重复的问题

    这篇文章主要介绍了MySQL使用distinct去掉查询结果重复的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01

最新评论