在数据库操作中,事务是确保数据一致性和完整性的核心机制。本文将深入探讨事务的ACID特性、隔离级别、并发问题以及MySQL底层是如何通过MVCC等技术实现这些特性的。
一、 为什么需要事务?一个经典案例
想象一个简单的卖票场景:一张票,两个客户端A和B同时来购买。
客户端A检查票数,发现大于0。
在A执行出票并更新数据库之前,客户端B也检查了票数,同样发现大于0。
于是,客户端B也执行了出票操作。
最终,同一张票被卖出了两次。
这个问题的根源在于,一组相关的数据库操作(检查票数、扣减票数)没有被当作一个不可分割的原子操作来执行。为了解决这类问题,事务应运而生。一个可靠的买票过程需要满足:
原子性:买票过程(检查+扣减)是一个整体,要么全部成功,要么全部失败。
隔离性:多个买票过程不能互相干扰。
持久性:一旦出票成功,结果就是永久性的。
一致性:买票前后,数据库的约束(如票数不能为负)始终得到满足。
二、 什么是事务?
事务是由一组DML(数据操作语言)语句组成的逻辑单元。这个单元内的所有语句作为一个整体,要么全部成功执行(提交,Commit),要么全部失败回滚(Rollback)。
一个典型的事务例子是删除一个学生的所有信息:这需要删除基本信息表、成绩表、活动记录表等多张表中的数据。这些操作必须作为一个事务,否则如果只删除了部分信息,数据库就会处于不一致的状态。
三、 事务的ACID特性
ACID是衡量事务的四个核心特性:
原子性:事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性是最终目的,原子性、隔离性、持久性都是为了保障一致性。
隔离性:数据库系统允许多个并发事务同时对其数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性:一旦事务被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作或故障不应该对其有任何影响。
注意:在MySQL中,并非所有存储引擎都支持事务。常用的InnoDB引擎支持事务,而MyISAM引擎不支持。
四、 事务的基本操作
1. 事务的提交方式
自动提交:MySQL默认模式,每一条SQL语句都是一个独立的事务。
SHOW VARIABLES LIKE 'autocommit'; -- 查看状态(通常为ON) SET autocommit = 0; -- 关闭自动提交手动提交:需要显式地开始事务和提交事务。
START TRANSACTION; -- 或 BEGIN -- 一系列DML操作... COMMIT; -- 提交事务 -- 或 ROLLBACK; -- 回滚事务2. 关键操作演示
START TRANSACTION/BEGIN:显式开启一个事务。COMMIT:提交事务,使修改永久生效。ROLLBACK:回滚事务,撤销所有未提交的修改。SAVEPOINT:在事务中创建保存点,可以回滚到指定保存点。
SAVEPOINT savepoint_name; ROLLBACK TO savepoint_name;重要结论:
只要使用了
BEGIN或START TRANSACTION,事务必须通过COMMIT才会持久化,与autocommit的设置无关。事务可以手动回滚,并且当操作异常(如客户端崩溃)时,MySQL会自动回滚未提交的事务。
五、 事务的隔离性与并发问题
当多个事务并发执行时,可能会引发以下问题:
问题 | 描述 |
|---|---|
脏读 | 一个事务读到了另一个未提交事务修改的数据。 |
不可重复读 | 在同一个事务中,多次读取同一数据,得到的结果不同(重点在于其他事务对数据进行了修改或删除)。 |
幻读 | 在同一个事务中,多次查询符合条件的记录数量,发现数量发生了变化(重点在于其他事务新增了数据)。 |
为了解决这些问题,SQL标准定义了4种隔离级别,隔离级别由低到高,并发性能由高到低。
六、 事务的隔离级别
MySQL支持四种隔离级别:
读未提交:一个事务可以读取另一个未提交事务的数据。存在脏读、不可重复读、幻读问题。
读已提交:一个事务只能读取另一个已提交事务的数据。解决脏读,但存在不可重复读和幻读。
可重复读:MySQL的默认隔离级别。确保在同一事务中,多次读取同一数据的结果是一致的。解决脏读、不可重复读,通过Next-Key锁在一定程度上解决了幻读。
串行化:最高隔离级别,所有事务逐个执行。解决所有并发问题,但性能极低,几乎不用。
查看与设置隔离级别:
-- 查看当前会话隔离级别 SELECT @@session.tx_isolation; -- MySQL 5.7 SELECT @@transaction_isolation; -- MySQL 8.0+ -- 设置当前会话隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;各级别问题汇总:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
可重复读 | × | × | △* |
串行化 | × | × | × |
*△:InnoDB在可重复读级别下通过Next-Key锁避免了幻读。
七、 事务隔离性的底层原理:MVCC
MySQL的InnoDB引擎通过多版本并发控制(MVCC) 机制来实现高并发的快照读,而非单纯依靠加锁。
1. 核心概念
隐藏字段:每行记录都有一些隐藏字段:
DB_TRX_ID:最近修改/创建该记录的事务ID。DB_ROLL_PTR:回滚指针,指向该记录的上一个历史版本(存储在undo log中)。DB_ROW_ID:隐含的自增ID(若表无主键)。
undo log:一种日志,用于存储记录的历史版本。当记录被更新时,旧数据会作为版本链的一部分存入undo log。
Read View:事务在执行快照读时产生的读视图。它决定了当前事务能看到哪个版本的数据。它主要包含:
当前系统活跃事务ID列表。
低水位线:活跃事务列表中最小ID。
高水位线:系统下一个将要分配的事务ID。
2. MVCC工作原理
当执行一个快照读(如普通的SELECT)时,会生成一个Read View。然后遍历记录的历史版本链,通过一套可见性算法(比较数据版本的DB_TRX_ID与Read View中的高低水位线),找到对当前事务可见的那个版本数据。
3. RR与RC的本质区别
可重复读:在同一个事务中,只有第一次快照读会生成Read View。后续所有快照读都复用这个Read View。因此,在整个事务期间,看到的数据 snapshot 是一致的。
读已提交:在同一个事务中,每次快照读都会生成一个新的、最新的Read View。因此,每次读都能看到其他已提交事务的最新修改。
这就是RC级别下会出现“不可重复读”,而RR级别下不会的原因。
八、 总结
事务是保障数据库一致性的关键,ACID是它的核心特性。
隔离级别是并发控制和数据安全的权衡。MySQL默认的可重复读级别在保证数据一致性的同时,提供了良好的并发性能。
MVCC是InnoDB实现高并发读写的基石。它通过维护数据的历史版本和Read View机制,使得读操作不用阻塞写操作,写操作也不用阻塞读操作,极大地提升了数据库的并发处理能力。
理解事务及其底层原理,对于设计高可用、高一致性的数据库应用至关重要。