一、前言
- 事务是关系型数据库中的重要概念,用于保证一组数据库操作作为一个单独的工作单元来执行。无论是银行转账、订单处理还是复杂的数据修改,事务都能保证操作的一致性和完整性。
- 本文将带您从基础概念到高级技巧,全面了解 MySQL 事务的工作原理、使用方法和优化策略。
二、事务基础
什么是事务?
- 定义:事务是一组操作的集合,这些操作要么全部执行,要么全部不执行。
- 事务的目的:确保数据库操作的一致性、完整性、隔离性和持久性(ACID 属性)。
事务的 ACID 属性
原子性 (Atomicity)
事务是不可分割的最小操作单位,要么全部执行,要么全部不执行。主要是commit、rollback、autocommit来保证原子性。
一致性 (Consistency)
所谓一致性,就是保证数据的一致,也就是保证数据不要丢失,不会因为突然的断电等导致数据与想要的数据不一致。主要体现在:
- 双写 保证内存跟磁盘之间同步的数据安全
- 基于RedoLog的数据恢复
隔离性 (Isolation)一个事务的执行不应受其他事务的干扰。主要体现在:
- 事务的隔离级别
- InnoDb的锁机制
持久性 (Durability)
一旦事务提交,对数据库的修改是永久性的,即使系统崩溃也不会丢失。InnoDB去保证持久性主要体现在:
- 双写 保证内存同步到磁盘,就算page损坏的情况下也能恢复
- RedoLog的同步机制
- binlog的同步机制
事务的开启、提交、回滚
START TRANSACTION -- 开始一个事务。
COMMIT -- 提交事务,保存所有的更改。
ROLLBACK -- 回滚事务,撤销事务中的所有更改。
自动提交
大家想一下我们平时写sql为什么没加上面的那些事务语句呢?这是因为我们Mysql里面,Mysql默认以自动提交模式运行的,简单来讲,就是每个语句,当没有START TRANSACTION开启事务的时候,每个语句都默认START TRANSACTION、commit包围,并且不能用ROLLBACK来回滚。但是如果执行期间发生了错误,则进行回滚。
查询是否开启自动提交
show SESSION VARIABLES like 'autocommit'; -- 查询(会话)是否开启自动提交
show GLOBAL VARIABLES like 'autocommit'; -- 查询(全局)自动开启提交
三、MySQL 中事务
大家想想,如果没有事务,数据的查询会有哪些问题呢?
数据一致性问题
脏读: 能读取到其他线程还没有提交的数据;但是这些数据可能是会回滚的。
图片
不可重复读: 在开启事务之后,读取到其他事务进行修改或者删除提交的的数据。
图片
幻读: 在开启事务之后,读到了其他事务新添加的新数据。
图片
非锁定一致性读取(MVCC,快照读)
既然有上面那些问题,那么我们看看mysql是怎么解决这些问题的,首先我们需要先来了解几个知识点。
事务的隔离级别
- READ UNCOMMITTED:最低隔离级别,允许事务读取未提交的数据(脏读)。
- READ COMMITTED:保证事务只能读取已提交的数据,避免脏读,但可能会发生不可重复读。
- REPEATABLE READ:事务中的查询在整个事务期间保持一致,避免了不可重复读,但可能会有幻读。(innodb默认事务隔离级别)
- SERIALIZABLE:最高隔离级别,强制事务串行执行,避免所有并发问题,但性能开销大。
ReadView结构
m_low_limit_id:即将要分配的下一个事务ID。
m_up_limit_id:所有存活的(没有提交的)事务ID中最小值。
m_creator_trx_id:创建这个readView的事务ID。
m_ids:创建readView时,所有存活的事务ID列表。
行隐藏字段
DB_TRX_ID:更新这行数据的事务ID。
DB_ROLL_PTR :回滚指针,被改动前的undolog日志指针。
判断是否可见逻辑
有了上面几个知识点,我们来看看ReadView怎么跟我的行数据的DB_TRX_ID来配合 做到解决我的不可
重复读或者幻读问题呢?(这里就不画图了,画图太复杂了,大家多理解下下面的规则)
- 如果数据的DB_TRX_ID < m_up_limit_id, 都小于存活的事务ID了,那么肯定不存活了,说明在创建ReadView的时候已经提交了,可见。意思就是,A事务已经提交了,B事务才开始查询,那么肯定可以查询到最新的数据。
- 如果数据的DB_TRX_ID >=m_low_limit_id, 大于等于我即将分配的事务ID, 那么表明修改这条数据的事务是在创建了ReadView之后开启的,不可见。
- 如果 m_up_limit_id<= DB_TRX_ID< m_low_limit_id, 表明修改这条数据的事务在第一次快照之前就创建好了,但是不确定提没提交,判断有没有提交,直接可以根据活跃的事务列表 m_ids判断
DB_TRX_ID如果在m_ids中,表面在创建ReadView之时还没提交,不可见
DB_TRX_ID如果不在m_ids,表面在创建ReadView之时已经提交,可见
UndoLog日志查询以往版本的数据
所谓UndoLog,也就是回滚日志,简单点,当我需要回滚的时候,能找到之前相关的数据。比如,我们rollback或者异常中断的时候,能找到改动之前的数据进行恢复。当然,我们在mvcc中也需要用到undolog来找到以往的数据来解决不可重复读跟幻读问题。
那么undolog到底怎么记录的,我们来看下面这个图:
图片
四、事务的并发控制
锁机制
**行级锁 (Row-level Lock)**:InnoDB 默认使用行级锁,它允许更多的并发操作,减少锁冲突。
**表级锁 (Table-level Lock)**:MyISAM 使用表级锁,可能导致较大的锁竞争。
锁的类型
记录锁(Record Locks)
记录锁,顾名思义,是锁在索引记录上的锁,索引扫描某些数据的时候,在这些索引数据上加锁。
SELECT * FROM user where id=1 FOR UPDATE; -- 索引扫描到id=1的数据,那么会锁id=1的数据,其他事务不能进行操作
间隙锁(Gap Locks)
间隙锁是对索引记录之间的间隙的锁。所谓间隙,就是索引数据之间的间隙,那么间隙锁,就是锁住数据之间的间隙,不允许间隙之内添加数据。看看下面的例子:
SELECT * FROM user where id > 1 AND id < 5 FOR UPDATE; -- 这里因为没有命中索引,所以索引1 和 5 之间不能添加id为2,3,4的数据
临键锁(Next-Key Locks)
临键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。就是我扫描的数据,既包含索引中存在的数据,又是扫描的一个区间。看看下面的例子:
SELECT * FROM user where id > 4 AND id < 8 FOR UPDATE; -- 这里命中了索引4,所以索引1 和 5 之间和5和9之间不能添加id为2,3,4,6,7,8的数据,并且id=4这条数据不能修改
五、总结
- 事务是 MySQL 数据库中重要的概念,它保证了操作的原子性、一致性、隔离性和持久性(ACID 属性)。
- MySQL 提供了丰富的事务控制功能,包括事务隔离级别、锁机制、死锁检测等。