尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MySQL 事务入门详解:ACID 四大特性与完整事务实操演练

MySQL 事务入门详解:ACID 四大特性与完整事务实操演练 小叶-duck个人主页❄️个人专栏《Data-Structure-Learning》《C入门到进阶自我学习过程记录》《Linux系统从入门到实践》《Linux网络从入门到实践》《Qt 方寸极境》 《MySQL》✨未择之路不须回头已择之路纵是荆棘遍野亦作花海遨游目录前言一、事务基础认知1.1 业务场景无事务下的数据异常现象1.2 事务标准定义一组不可分割的 SQL 执行单元二、事务四大核心特性 ACID2.1 原子性Atomicity要么全部成功要么全部失败2.2 一致性Consistency事务执行前后数据合法合规2.3 隔离性Isolation并发事务之间相互不受干扰2.4 持久性Durability事务提交后数据永久生效三、MySQL 事务引擎支持范围3.1 查看数据库支持的引擎四、事务的提交方式4.1 查看当前提交方式4.2 修改自动提交模式五、事务的核心操作含完整实操案例5.1 事务基础操作语法5.2 正常演示事务的开启、保存点、回滚与提交5.3 异常场景验证事务的原子性与持久性实验 1未 commit客户端崩溃MySQL 自动回滚实验 2commit 后客户端崩溃数据永久生效实验 3begin 会自动忽略 autocommit 设置5.4 事务操作注意事项结束语前言在日常开发中转账、订单状态变更、库存扣减这类业务往往需要多条 SQL 协同执行。倘若中间程序意外崩溃一部分 SQL 执行成功、一部分执行失败数据就会陷入错乱。数据库事务正是用来解决这类问题的核心方案。 本文将从一个直观的业务案例入手讲解事务的定义与 ACID 四大特性同时搭配大量可复现的实验演示事务提交、回滚、保存点以及自动提交机制。掌握事务基础操作是理解并发事务、隔离级别底层原理的必经之路。一、事务基础认知1.1 业务场景无事务下的数据异常现象我们先从一个经典的火车票售票系统场景切入这也是所有业务并发场景的缩影有一张火车票库存表tickets数据如下idnamenums10西安 - 兰州1此时有两个客户端同时抢这张票执行逻辑完全一致检查票数是否大于 0如果有票执行卖票逻辑将票数 - 1在没有事务控制的情况下会出现如下时序问题客户端 A 检查票数发现还有 1 张票准备执行更新但还没写入数据库客户端 B 同时检查票数也看到了 1 张票同样进入卖票逻辑客户端 A 执行update tickets set nums nums-1 where id10票数变为 0客户端 B 也执行更新最终票数变为 - 1同一张票被卖了两次出现超卖问题。要解决这个问题就需要让整个抢票流程满足 4 个核心要求抢票的整个过程必须是原子的要么全成要么全败两个抢票的操作不能互相干扰卖票成功后数据必须永久生效不能因为宕机丢失卖票前后库存数据的状态必须是一致的不能出现负数库存。而这 4 个要求正是 MySQL事务的四大核心特性——ACID。1.2 事务标准定义一组不可分割的 SQL 执行单元事务是由一组逻辑相关的 DML 语句组成的执行单元这组语句要么全部执行成功要么全部执行失败回滚是一个不可分割的整体。MySQL 提供了完整的事务机制来保证我们的业务逻辑在并发场景下的数据安全。事务就是要做的或所做的事情主要用于处理操作量大复杂度高的数据。假设一种场景你毕业了学校的教务系统后台 MySQL 中不再需要你的数据要删除你的所有信息 (假设场景), 那么要删除你的基本信息 (姓名电话籍贯等) 的同时也删除和你有关的其他信息比如你的各科成绩你在校表现甚至你在论坛发过的文章等。这样就需要多条 MySQL 语句构成那么所有这些操作合起来就构成了一个事务。正如我们上面所说一个 MySQL 数据库可不止你一个事务在运行同一时刻甚至有大量的请求被包装成事务在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL最多很多 SQL, 这样如果大家都访问同样的表数据在不加保护的情况就绝对会出现问题。甚至因为事务由多条 SQL 构成那么也会存在执行到一半出错或者不想再执行的情况那么已经执行的怎么办呢所有一个完整的事务绝对不是简单的 sql 集合还需要满足接下来要详细讲解的四个属性。二、事务四大核心特性 ACIDACID 是事务的灵魂也是面试必问的核心考点四个特性环环相扣缺一不可。2.1 原子性Atomicity要么全部成功要么全部失败原子性指的是一个事务中的所有操作要么全部完成要么全部不完成不会结束在中间某个环节。事务在执行过程中发生错误会被回滚Rollback到事务开始前的状态就像这个事务从来没有执行过一样。就像银行转账A 扣钱和 B 加钱必须同时成功只要有一步失败两边的钱都要恢复到初始状态。2.2 一致性Consistency事务执行前后数据合法合规一致性指的是事务开始之前和事务结束以后数据库的完整性约束没有被破坏。这里的完整性包括数据的精确度、串联性、业务规则约束。比如转账场景中转账前后 A 和 B 的账户总金额必须保持一致售票场景中库存不能出现负数。一致性是事务的最终归宿原子性、隔离性、持久性都是为了保证数据库的一致性。2.3 隔离性Isolation并发事务之间相互不受干扰隔离性指的是数据库允许多个并发事务同时对数据进行读写和修改隔离性可以防止多个事务并发执行时因为交叉执行导致的数据不一致。多个事务同时操作同一张表、同一行数据时就像多个同学同时在一个教室学习隔离性就是给每个同学拉上了帘子保证大家的学习互不干扰。MySQL 提供了 4 种隔离级别来适配不同的业务并发场景后面会详细拆解。2.4 持久性Durability事务提交后数据永久生效持久性指的是事务处理结束后对数据的修改就是永久的即便系统故障、数据库宕机修改的数据也不会丢失。事务一旦提交成功数据就会被持久化到磁盘中不会因为任何故障回滚。比如你在 ATM 机取了钱交易提交成功后哪怕银行机房断电你的账户扣款记录也不会消失。三、MySQL 事务引擎支持范围不是所有 MySQL 引擎都支持事务只有 InnoDB 引擎支持完整的事务特性这也是 InnoDB 成为 MySQL 默认引擎的核心原因之一而 MyISAM 引擎完全不支持事务。3.1 查看数据库支持的引擎我们可以通过以下命令查看当前 MySQL 支持的所有引擎以及事务支持情况-- 表格形式展示 show engines; -- 行形式展示查看更清晰 show engines \G执行后核心结果如下*************************** 1. row *************************** Engine: InnoDB Support: DEFAULT Comment: Supports transactions, row-level locking, and foreign keys Transactions: YES XA: YES Savepoints: YES *************************** 5. row *************************** Engine: MyISAM Support: YES Comment: MyISAM storage engine Transactions: NO XA: NO Savepoints: NO可以清晰看到InnoDB 的 Transactions 字段为YES支持事务而 MyISAM 为NO不支持事务。四、事务的提交方式MySQL 事务有两种提交方式自动提交和手动提交我们可以通过参数控制。4.1 查看当前提交方式show variables like autocommit;默认结果---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.41 sec)autocommit ON 表示开启自动提交默认情况下我们执行的每一条 DML 语句都会被 MySQL 自动封装成一个事务执行完成后自动提交。上面这句话凭什么这么说下面我们会进行详细的解释。4.2 修改自动提交模式用SET来改变 MySQL 的自动提交模式关闭自动提交mysql SET AUTOCOMMIT0; -- 禁止自动提交 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 AUTOCOMMIT1; -- 开启自动提交 Query OK, 0 rows affected (0.00 sec) mysql show variables like autocommit; ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.01 sec)注意autocommit0关闭自动提交后你执行的所有DML 语句都不会立即生效需要手动执行 commit 才会提交执行 rollback 会回滚只要我们执行 begin/start transaction手动开启一个事务无论 autocommit 是开启还是关闭都必须手动 commit 才会提交。这些我们都会在后面的实验中进行证明。五、事务的核心操作含完整实操案例我们先创建一张银行账户测试表后续所有演示都基于这张表-- 创建账户表必须使用InnoDB引擎 create table if not exists account ( id int primary key, name varchar(50) not null default , blance decimal(10,2) not null default 0.0 ) engineinnodb default charsetutf8;并且为了便于演示我们将 mysql 的默认隔离级别设置成读未提交。具体隔离级别是什么怎么设置隔离级别在下一篇文章我们会着重进行讲解隔离级别相关内容。这里已使用为主。mysql set global transaction isolation level READ UNCOMMITTED; Query OK, 0 rows affected (0.00 sec)5.1 事务基础操作语法操作语句作用说明begin; / start transaction;手动开启一个事务推荐使用begin更简洁commit;提交事务将事务中所有修改永久生效rollback;回滚事务撤销事务中所有未提交的修改恢复到事务开启前的状态savepoint 保存点名;创建事务保存点用于部分回滚rollback to savepoint 保存点名;回滚到指定的保存点不影响保存点之前的操作5.2 正常演示事务的开启、保存点、回滚与提交-- 查看事务是否自动提交。我们故意设置成自动提交看看该选项是否影响begin show variables like autocommit; ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.00 sec) -- 开始一个事务begin也可以推荐begin start transaction; -- 创建一个保存点save1 savepoint save1; -- 插入一条记录 insert into account values (1, 张三, 100); -- 创建一个保存点save2 savepoint save2; -- 再插入一条记录 insert into account values (2, 李四, 10000); -- 查询表数据此时两条记录都存在 select * from account; ------------------- | id | name | blance | ------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | ------------------- 2 rows in set (0.00 sec) -- 回滚到保存点save2 rollback to save2; -- 查询表数据李四这条数据被撤销只剩张三 select * from account; ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec) -- 整体回滚回滚至事务起始位置 rollback; -- 查询表数据事务内所有操作全部撤销 select * from account; Empty set (0.00 sec)5.3 异常场景验证事务的原子性与持久性通过 4 个核心实验验证了事务的核心特性这里完整还原SQL 全小写实验 1未 commit客户端崩溃MySQL 自动回滚-- 终端A操作 select * from account; -- 表内无数据 Empty set (0.00 sec) show variables like autocommit; -- autocommitON ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.00 sec) begin; -- 开启事务 Query OK, 0 rows affected (0.00 sec) insert into account values (1, 张三, 100); Query OK, 1 row affected (0.00 sec) select * from account; -- 能看到插入的数据此时未commit ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec) -- 此时强制终止终端ctrl\ 异常退出 -- 终端B操作 -- 终端A崩溃前能读到未提交的数据读未提交隔离级别下 select * from account; ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec) -- 终端A崩溃后再次查询数据自动回滚表内无数据 select * from account; Empty set (0.00 sec)结论事务未提交时客户端异常崩溃MySQL 会自动回滚整个事务保证原子性。实验 2commit 后客户端崩溃数据永久生效--终端 A mysql show variables like autocommit; -- 依旧自动提交 ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.00 sec) mysql select * from account; -- 当前表内无数据 Empty set (0.00 sec) mysql begin; -- 开启事务 Query OK, 0 rows affected (0.00 sec) mysql insert into account values (1, 张三, 100); -- 插入记录 Query OK, 1 row affected (0.00 sec) mysql commit; --提交事务 Query OK, 0 rows affected (0.04 sec) mysql Aborted -- ctrl \ 异常终止MySQL --终端 B mysql select * from account; --数据存在了所以commit的作用是将数据持久化到MySQL中 ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec)结论事务一旦 commit 提交数据就会持久化到磁盘无论客户端是否崩溃数据都不会丢失符合持久性特性。实验 3begin 会自动忽略 autocommit 设置-- 终端 A mysql select *from account; --查看历史数据 ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec) mysql show variables like autocommit; --查看事务提交方式 ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.00 sec) mysql set autocommit0; --关闭自动提交 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 begin; --开启事务 Query OK, 0 rows affected (0.00 sec) mysql insert into account values (2, 李四, 10000); --插入记录 Query OK, 1 row affected (0.00 sec) mysql select *from account; --查看插入记录同时查看终端B -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec) mysql Aborted --再次异常终止 -- 终端B mysql select * from account; --终端A崩溃前 -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec) mysql select * from account; --终端A崩溃后自动回滚 ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------结论只要执行 begin 手动开启事务无论 autocommit 是开启还是关闭都必须手动commit 才会生效未提交的异常退出会自动回滚。也就是说begin 开启事务会创建独立事务上下文不受 autocommit 干扰。实验 4单条 SQL 与自动提交的关系场景一关闭autocommit单条SQL未commit崩溃后回滚--场景一关闭autocommit单条SQL未commit崩溃后回滚 -- 终端A mysql select * from account; ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec) mysql show variables like autocommit; ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.00 sec) mysql set autocommit0; --关闭自动提交 Query OK, 0 rows affected (0.00 sec) mysql insert into account values (2, 李四, 10000); --插入记录 Query OK, 1 row affected (0.00 sec) mysql select *from account; --查看结果已经插入。此时可以查看终端B -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec) mysql ^DBye --ctrl \ or ctrl d,终止终端 --终端B mysql select * from account; --终端A崩溃前 -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec) mysql select * from account; --终端A崩溃后 ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec)场景二开启autocommit单条SQL自动提交崩溃后不回滚-- 场景二开启autocommit单条SQL自动提交崩溃后不回滚 --终端A mysql show variables like autocommit; --开启默认提交 ---------------------- | Variable_name | Value | ---------------------- | autocommit | ON | ---------------------- 1 row in set (0.00 sec) mysql select * from account; ------------------ | id | name | blance | ------------------ | 1 | 张三 | 100.00 | ------------------ 1 row in set (0.00 sec) mysql insert into account values (2, 李四, 10000); Query OK, 1 row affected (0.01 sec) mysql select *from account; --数据已经插入 -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec) mysql Aborted --异常终止 --终端B mysql select * from account; --终端A崩溃前 -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec) mysql select * from account; --终端A崩溃后并不影响已经持久化。autocommit起作用 -------------------- | id | name | blance | -------------------- | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | -------------------- 2 rows in set (0.00 sec)结论autocommitON 时InnoDB 会把每一条单 SQL 都封装成事务自动提交autocommitOFF 时所有 SQL 都需要手动 commit 才会生效。5.4 事务操作注意事项如果没有设置保存点也可以执行 rollback只能回滚到事务开启前的状态前提是事务还没有提交事务一旦执行 commit 提交就不能再执行 rollback 回滚了可以选择回滚到任意一个已创建的保存点只有 InnoDB 引擎支持事务和保存点MyISAM 不支持开启事务推荐使用 begin 或 start transaction结束语本篇我们从业务场景出发认识了事务是什么完整梳理了 ACID 四大特性的含义同时结合多组实验验证了事务提交、回滚、异常断开时的行为表现。 事务是数据库保障数据可靠的基础工具清楚事务基础语法与运行行为是理解并发事务问题的前提。下一篇我们将把视角转向并发场景探讨多事务同时读写引发的数据异常深入学习事务隔离级别与 MVCC 底层实现原理。
返回列表