概念
MySQL中的事务:是一个或多个SQL语句组成的一个执行单元,这些 SQL 语句要么全部执行成功,要么全部不执行,不会出现部分执行的情况。事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
MySQL事务的四大特征?
事务的主要作用是保证数据库操作的一致性,即事务内的操作,要么全部成功,要么全部失败回滚,不会出现中间状态。这对于维护数据库的完整性和一致性非常重要。
事务具有四个基本特性,也就是通常所说的 ACID 特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
什么是原子性?
原子性子性意味着事务中的所有操作要么全部完成,要么全部不完成,它是不可分割的单位。如果事务中的任何一个操作失败了,整个事务都会回滚到事务开始之前的状态,如同这些操作从未被执行过一样。
什么是一致性?
一致性确保事务从一个一致的状态转换到另一个一致的状态。
比如在银行转账事务中,无论发生什么,转账前后两个账户的总金额应保持不变。假如 A 账户(100 块)给 B 账户(10 块)转了 10 块钱,不管成功与否,A 和 B 的总金额都是 110 块。
什么是隔离性?
隔离性意味着并发执行的事务是彼此隔离的,一个事务的执行不会被其他事务干扰。就是事务之间是井水不犯河水的。
隔离性主要是为了解决事务并发执行时可能出现的问题,如脏读、不可重复读、幻读等。
数据库系统通过事务隔离级别(如读未提交、读已提交、可重复读、串行化)来实现事务的隔离性。
什么是持久性?
持久性确保事务一旦提交,它对数据库所做的更改就是永久性的,即使发生系统崩溃,数据库也能恢复到最近一次提交的状态。通常,持久性是通过数据库的恢复和日志机制来实现的,确保提交的事务更改不会丢失。
简短一点的回答可以是:
- 原子性:事务的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务中的操作不能只执行其中一部分。
- 一致性:事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致性与业务规则有关,比如银行转账,不论事务成功还是失败,转账双方的总金额应该是不变的。
- 隔离性:多个并发事务之间需要相互隔离,即一个事务的执行不能被其他事务干扰。
- 持久性:一旦事务提交,则其所做的修改将永久保存到数据库中。即使发生系统崩溃,修改的数据也不会丢失。
那ACID靠什么来保证呢?
MySQL 通过事务、undo log、redo log 来确保 ACID。
如何保证原子性?
MySQL 通过 undo log 来确保原子性(Atomicity)。
当事务开始时,MySQL 会在undo log
中记录事务开始前的旧值。如果事务执行失败,MySQL 会使用undo log
中的旧值来回滚事务开始前的状态;如果事务执行成功,MySQL 会在某个时间节点将undo log
删除。
如何保证一致性?
如果其他三个特性都得到了保证,那么一致性(Consistency)就自然而然得到保证了。
如何保证隔离性?
MySQL 定义了多种隔离级别,通过 MVCC 来确保每个事务都有专属自己的数据版本,从而实现隔离性(Isolation)。
在 MVCC 中,每行记录都有一个版本号,当事务尝试读取记录时,会根据事务的隔离级别和记录的版本号来决定是否可以读取。
如何保证持久性?
redo log 是一种物理日志,当执行写操作时,MySQL 会先将更改记录到 redo log 中。当 redo log 填满时,MySQL 再将这些更改写入数据文件中。
如果 MySQL 在写入数据文件时发生崩溃,可以通过 redo log 来恢复数据文件,从而确保持久性(Durability)。
上面提到隔离级别?那事务有哪一些隔离级别?MySQL的默认隔离级别是什么?
事务的隔离级别定了一个事务可能受其他事务影响的程度,MySQL 支持的四种隔离级别分别是:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
什么是读未提交?
读未提交是最低的隔离级别,在这个级别,当前事务可以读取未被其他事务提交的数据,以至于会出现“脏读”、“不可重复读”和“幻读”的问题。
什么是读已提交?
当前事务只能读取已经被其他事务提交的数据,可以避免“脏读”现象。但不可重复读和幻读问题仍然存在。
什么是可重复读?
确保在同一事务中多次读取相同记录的结果是一致的,即使其他事务对这条记录进行了修改,也不会影响到当前事务。
可重复读是 MySQL 默认的隔离级别,避免了“脏读”和“不可重复读”,但可能会出现幻读。
什么是串行化?
最高的隔离级别,通过强制事务串行执行来避免并发问题,可以解决“脏读”、“不可重复读”和“幻读”问题。
但会导致大量的超时和锁竞争问题。
A事务未提交,B事务上查询到的是旧值还是新值?
在 MySQL 的默认隔离级别(可重复读)下,如果事务 A 修改了数据但未提交,事务 B 将看到修改之前的数据。
这是因为在可重复读隔离级别下,MySQL 将通过多版本并发控制(MVCC)机制来保证一个事务不会看到其他事务未提交的数据,从而确保读一致性。
上面提到的脏读、不可重复读、幻读是什么?
脏读
指的是一个事务能够读取另一个事务尚未提交的数据。如果读到的数据在之后被回滚了,那么第一个事务读取到的就是无效的数据。
-- 事务 A
START TRANSACTION;
UPDATE employees SET salary = 5000 WHERE id = 1;
-- 事务 B
START TRANSACTION;
SELECT salary FROM employees WHERE id = 1; -- 读取到 salary = 5000 (脏读)
ROLLBACK;
事务A未提交。事务B读到的数据就是脏读,事务A回滚,B读到的数据就是无效的。
不可重复读
修改
指的是在同一事务中执行相同的查询时,返回的结果集不同。这是由于在事务过程中,另一个事务修改了数据并提交。
比如说事务 A 在第一次读取某个值后,事务 B 修改了这个值并提交,事务 A 再次读取时,发现值已经改变。
-- 事务 A
START TRANSACTION;
SELECT salary FROM employees WHERE id = 1; -- 读取到 salary = 3000
-- 事务 B
START TRANSACTION;
UPDATE employees SET salary = 5000 WHERE id = 1;
COMMIT;
-- 事务 A 再次读取
SELECT salary FROM employees WHERE id = 1; -- 读取到 salary = 5000 (不可重复读)
COMMIT;
在此案例中,事务A首先读到的数据为3000,后面事务B修改了器值为5000,并提交了事务,最后A在重新读数据,导致读到的数据是B修改过的数据5000。导致两次读到的数据不一致。这就是不可重复读,我们要的是可重复读,也就是两次读到的数据是一致的。
幻读
插入
指的是在同一事务中执行相同的查询时,返回的结果集中出现了之前没有的数据行。这是因为在事务过程中,另一个事务插入了新的数据并提交。
比如说事务 A 在第一次查询某个条件范围的数据行后,事务 B 插入了一条新数据且符合条件范围,事务 A 再次查询时,发现多了一条数据。
-- 事务 A
START TRANSACTION;
SELECT * FROM employees WHERE department = 'HR'; -- 读取到 10 条记录
-- 事务 B
START TRANSACTION;
INSERT INTO employees (id, name, department) VALUES (11, 'John Doe', 'HR');
COMMIT;
-- 事务 A 再次查询
SELECT * FROM employees WHERE department = 'HR'; -- 读取到 11 条记录 (幻读)
COMMIT;
可以通过设置隔离级别为可串行化来避免幻读,代价是降低并发性能。
在不同隔离级别下,并发事务下会发生可能的问题有?
那事务的各个隔离级别都是如何实现的?
读未提交如何实现?
不提供任何锁机制来保护读取的数据,允许读取未提交的数据(即脏读)。
读已提交&可重复读是如何实现的?
读已提交和可重复读通过 MVCC 机制中的 ReadView 来实现。
- READ COMMITTED:每次读取数据前都生成一个 ReadView,保证每次读操作都是最新的数据。
- REPEATABLE READ:只在第一次读操作时生成一个 ReadView,后续读操作都使用这个 ReadView,保证事务内读取的数据是一致的。
串行化是如何实现的?
事务在读操作时,必须先加表级共享锁,直到事务结束才释放;事务在写操作时,必须先加表级排他锁,直到事务结束才释放。