文章目录
在数据库应用开发中,数据的完整性和一致性至关重要。MySQL 事务管理机制能够确保一组相关操作要么全部成功执行,要么全部失败回滚,有效保障了数据的可靠性。今天,我们就一起来深入探讨 MySQL 事务管理的相关知识。
一、事务概述
事务是逻辑上的一组操作,这些操作要么全执行,要么全不执行,是一个不可分割的整体。比如常见的银行转账场景,从 A 账户扣除一定金额,同时向 B 账户增加相同金额,这两个操作必须同时成功或者同时失败,否则就会出现数据不一致的问题,而这两个操作就可以构成一个事务。
二、事务的特性(ACID)
原子性(Atomicity)
事务是最小的执行单位,不允许分割。就像前面提到的转账操作,不能只完成从 A 账户扣款,却不增加 B 账户的金额。如果事务执行过程中出现错误,所有已执行的操作都会被回滚,就如同这个事务从未执行过一样。原子性是通过 InnoDB 存储引擎的 undo log 来实现的,当事务对数据库进行修改时,InnoDB 会生成 undo log;若事务执行失败或回滚,就利用 undo log 中的信息将数据恢复到修改前的状态。
一致性(Consistency)
执行事务的前后,数据要保持一致状态。例如,在一个简单的库存管理系统中,商品的总库存数量在进行出库或入库事务前后应该是符合业务规则的。一致性不仅依赖于数据库层面的保障,也需要应用层面的配合,确保事务的操作符合所有预设规则。
隔离性(Isolation)
当多个事务并发访问数据库时,一个用户的事务不应该被其他事务所干扰,各并发事务之间数据库是独立的。MySQL 提供了不同的隔离级别来控制事务之间的隔离程度:
读未提交(Read Uncommitted):最低的隔离级别,允许读取尚未提交的数据变更。这种级别可能会导致脏读(读取到其他事务未提交的数据)、不可重复读(同一次事务中前后查询结果不一致)和幻读(事务执行过程中,前后查询的数据量发生变化)等问题。
读已提交(Read Committed):只允许读取并发事务已经提交的数据,可以避免脏读,但仍可能出现不可重复读和幻读问题。
可重复读(Repeatable Read):MySQL 中 InnoDB 存储引擎的默认隔离级别,保证在同一个事务中,对同一字段多次读取的结果都是一致的,除非本事务对其进行了修改。该级别可以避免脏读和不可重复读,但在某些情况下仍可能出现幻读。
串行化(Serializable):最高的隔离级别,所有事务依次执行(串行执行),可以完全避免脏读、不可重复读和幻读问题,但由于是串行执行,并发性能较低。
持久性(Durability)
一个事务被提交后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有影响。InnoDB 通过 redo log 来实现持久性,当数据被修改时,除了修改内存中的数据,还会在 redo log 中记录这次操作。事务提交时,会调用 fsync 接口对 redo log 进行刷盘。这样,如果 MySQL 宕机,重启时可以读取 redo log 中的数据,对数据库进行恢复。
三、事务的操作
事务的提交方式
自动提交:MySQL 默认每一条 DML(数据操作语言,如 INSERT、UPDATE、DELETE)语句都是一个单独的事务,即自动提交。可以通过show variables like ‘autocommit’;查看 autocommit 的状态,set autocommit=1;设置为自动提交(1 表示开启,0 表示关闭)。
手动提交:使用START TRANSACTION 或 BEGIN命令显式开启一个事务,然后执行一系列相关的 SQL 语句。如果所有操作都成功,使用COMMIT命令提交事务,使得对数据库的修改成为永久性;如果在执行过程中出现错误,则使用ROLLBACK命令回滚事务,撤销正在进行的所有未提交的修改。在手动提交事务中,还可以使用SAVEPOINT命令创建保存点,方便将事务回滚到指定的保存点,语法为SAVEPOINT savepoint_name;,回滚到保存点使用ROLLBACK TO SAVEPOINT savepoint_name; 。
查看和设置隔离级别
查看隔离级别:在 MySQL 5.7 及之前版本,可以使用select @@tx_isolation;查看当前会话的隔离级别;在 MySQL 8.0 中,使用SELECT @@transaction_isolation; 。
设置隔离级别:设置全局隔离级别使用SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别; ,设置当前会话隔离级别使用SET SESSION TRANSACTION ISOLATION LEVEL 隔离级别; ,其中 “隔离级别” 可以是READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE 中的一种。需要注意的是,设置全局隔离级别后,新创建的会话会采用新的隔离级别,而已经存在的会话不受影响;设置当前会话隔离级别只对当前会话有效。
四、事务常见问题
脏写 / 修改丢失
当两个事务在未提交的状况下,都修改同一条数据,若一个事务回滚,可能会把另一个事务修改的值也撤销。比如事务 A 和事务 B 同时修改某条记录,事务 A 先修改了值,事务 B 接着也做了修改,然后事务 A 回滚,那么事务 B 的修改也会被覆盖,就造成了脏写。
脏读
事务读取到其他事务未提交的数据。例如,事务 A 修改了某条数据但还未提交,此时事务 B 读取了这条被修改但未提交的数据,若事务 A 回滚,事务 B 读取的数据就是无效的,这就是脏读。
不可重复读
在同一次事务中前后查询不一致。比如在事务中先查询了某条记录的某个字段值,之后在事务未结束前,其他事务修改并提交了该记录,再次查询时得到的结果与第一次不同,这就是不可重复读。
幻读
在一次事务中前后数据量发生变化,导致用户产生不可预料的问题。通常发生在范围查询时,例如事务 A 查询满足某条件的一批数据,在事务 A 未结束时,其他事务插入了满足该条件的新数据,当事务 A 再次进行相同的范围查询时,结果中就多了新插入的数据,就像产生了幻觉一样,这就是幻读。
五、多版本并发控制(MVCC)
MVCC 是一种用来解决读 - 写冲突的无锁并发控制机制,能提高数据库并发读写的性能,同时还可以解决脏读、幻读、不可重复读等事务隔离问题。在并发读写数据库时,快照读(不加锁的 select 操作)不用阻塞写操作,写操作也不用阻塞读操作。
MVCC 的实现依赖于以下几个关键要素:
隐藏字段:记录中有一些隐藏字段,如DB_ROW_ID(隐含的自增 ID,当数据表没有主键时,InnoDB 会自动以它产生聚簇索引)、DB_TRX_ID(最近修改或插入事务 ID)、DB_ROLL_PTR(回滚指针,指向这条记录的上一个版本,通过链表形式组织)和DELETED_BIT(删除标记位,删除操作只是设置该标记位,并非真正删除记录,InnoDB 有专门的 purge 线程来清理标记为删除的记录) 。
undo log:用于事务的回滚操作,保证事务的原子性,同时还能构建数据修改之前的版本,支持多版本读。
Read View:事务进行快照读操作时产生的读视图,通过可见性算法,将当前事务与活跃事务进行比对分析,生成读视图。如果读取的数据的事务 ID 与当前事务 ID 相等,读取的就是最新数据;不相等时,就通过回滚指针遍历 undo log 链表,查找符合条件的记录的数据。