MySQL事务
一、数据库事务基础
1.1.什么是事务
简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。
比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一
1.2.事务隔离级别
当数据库上有多个事务同时执行的时候,可能出现以下问题
- 脏读(dirty read)
- 不可重复读(non-repeatable read)
- 幻读(phantom read)
为了解决这些问题,就有了隔离级别的概念
SQL 标准的事务隔离级别包括:
- 读未提交(read uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交(read committed):一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。 一个事务未提交变更对其他事务也是不可见的。
- 串行化(serializable ):顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
举个例子:

在不同的隔离级别下,V1、V2、V3的值分别是:
隔离级别 | V1 | V2 | V3 | 说明 |
---|
读未提交 | 2 | 2 | 2 | 虽然B事务还没提交,但是做的更改事务A也可以看到 | 读提交 | 1 | 2 | 2 | 事务B只有提交了,结果才能被事务A看到 | 可重复读 | 1 | 1 | 2 | 事务在执行期间看到的数据前后必须是一致的 | 串行化 | 1 | 1 | 2 | 事务B在执行“1改成2”的时候,会被锁住,等到事务A提交完成之后,才继续执行 |
通过以下命令查看数据库的默认隔离级别:
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ | // 可重复读
+-----------------------+-----------------+
1 row in set (0.01 sec)
为了实现可重复度读,MYSQL使用的机制是:每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
因此,对于长事务,这个回滚操作的记录就会很长,所以不建议使用长事务。
1.3.启动事务的方式
-
显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。不过注意,begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。 -
执行一条SQL语句,例如update t set k=k+1 where id=1 , 本身这就是一个事务,相当于隐式地执行了begin和commit。不过,如果设置了 set autocommit=0,此时这个线程的自动提交关掉。只要执行了一条语句(不管是查询还是插入更新),这个事务就启动了,而且并不会自动提交。这个事务持续存在直到主动执行 commit/rollback 语句,或者断开连接。
1.4.查询长事务语句
查询持续时间超过60s的事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
二、MVVC与事务隔离
2.1.什么是MVCC
MVCC(Multi-Version Concurrency Control)是为了实现事务的隔离性,通过数据的版本号,避免同一数据在不同事务间的竞争。
2.2.transaction id与row trx_id
事务开始的时候,都会向InnoDB的事务系统申请一个递增的transaction id。同时,而每行数据也都是有多个版本的(MVCC机制)。每次事务更新数据的时候,都会生成一个新的数据版本并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够拿到旧的版本数据。
下图是一个同一行数据4个版本的例子,此时最新的版本是V4 
图中的三个虚线箭头,就是 undo log;
V1、V2、V3 并不是物理上真实存在的,每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时候,就是通过 V4 依次执行 U3、U2 算出来。
也正是这种设计,让每个事务启动的时候,都有个全局的快照(基于整个库)。
2.3.可重复读和当前读
在 MySQL 里,有两个视图的概念:
-
一个是用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,查询方法与表一样。 -
另一个则是InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。InnoDB默认的事务隔离级别是可重复读,以下主要讨论这种级别。
在事务里面,一般就是查询和更新语句,对于查询语句,使用可重复读。读到的数据是事务启动前的数据。也就是事务执行期间,看到的数据保持一致。对于更新数据,都是先读后写的,而这个读,只能读当前的值(读提交)。
记住下面这两条原则就比较好分析问题了:
- 对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
- 对于当前读,总是读取已经提交完成的最新版本;
接下来看一个例子:
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

- 对于事务A,最终查询到的结果就是1。因为事务里面只有一个查询语句,使用可重复读。
- 事务B查询到的结果是3,因为当执行update语句的时候,使用读提交,此时拿到的数据,是事务C执行完成后的数据,这样也才能保证数据是最新的。
|