事务介绍
问题
有这样一个场景:A 与 B 去买票,此时还剩下最后一张票,A 先进行访问,A 发现有一张票,所以进行购买,当系统将票卖给 A,但是还没有执行更新数据库操作时,B 也对系统进行访问,B 发现还有一张票,于是也进行购买,那么一张票就被卖了两次,这是不合理的;
概念
概念:事务就是一组 DML 语句组成,这些语句在逻辑上存在相关性,这一组 DML 语句要么全部成功,要么全部失败,是一个整体,MySQL 提供一种机制,保证我们达到这样的效果,事务还规定不同的客户端看到的数据是不相同的; 举例:你毕业了,学校的教务系统后台 MySQL 中不再需要你的数据,此时要删除你的所有信息,那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如你的各科成绩,你在校表现,甚至你在论坛发过的文章等,这样就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务;
特性
原子性:一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样; 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏,这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作; 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,事务隔离分为不同级别,包括读未提交( Read uncommitted )、读已提交( read committed )、可重复读( repeatable read )和串行化( Serializable ); 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失;
版本支持
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 引擎不支持; show engines;
:查看引擎信息,以表格形式显示;show engines\g
:查看引擎信息,以行形式显示;
提交方式
show variables like 'autocommit';
:显示当前提交方式为哪种,ON——自动,OFF——手动;SET AUTOCOMMIT = 0;
:禁止自动提交;SET AUTOCOMMIT = 1;
:开启自动提交;
事务操作
指令介绍
begin;
:开始一个事务;start transaction;
:开始一个事务;savepoint 保存点;
:创建一个指定的保存点;rollback 保存点;
:回滚到指定保存点,在该保存点之后的所有操作都将无效,数据库恢复到创建该保存点时的状态;rollback;
:直接回滚到事务开始时的状态;commit;
:对所执行的事务操作进行提交,提交过后,所有保存点将失效,无法回滚;
总结
只要输入begin
或者start transaction
,事务便必须要通过commit
提交,才会持久化,与是否设置set autocommit
无关; 事务可以手动回滚,当操作异常时,MySQL 会自动回滚; 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交,受set autocommit
所设置的状态影响(select有特殊情况,因为 MySQL 有 MVCC);
事务隔离
概念
MySQL 服务可能会同时被多个客户端进程 / 线程访问,访问的方式以事务方式进行,而一个事务可能由多条 SQL 构成,也就意味着,任何一个事务都有执行前,执行中,执行后的阶段,而所谓的原子性,其实就是让用户层,要么看到执行前的状态,要么看到执行后的状态,执行中出现问题,可以随时回滚,所以单个事务,对用户表现出来的特性,就是原子性; 但毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个 SQL 的时候,就还是有可能会出现互相影响的情况,数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征——隔离性,数据库中,允许事务受不同程度的干扰,就有了一种重要特征——隔离级别;
问题
脏写:多个用户对同一个数据进行事务操作,当一个操作完毕后,还未提交时,另一个又对数据进行操作; 脏读:多个用户对数据进行操作,其中一个用户读到的内容是其他用户所执行事务且未提交的结果; 不可重复读:在执行一个事务的不同阶段,所读取到的同一字段数据值不一致,因为在执行事务过程中,其他事务对数据进行了操作(主要是修改和删除); 幻读:在执行一个事务的不同阶段,所读取到的数据条数不一致,因为在执行事务的过程中,是无法屏蔽别的用户进行insert
操作的,因为隔离性实现是对已存在数据加锁完成的,而insert
待插入的数据根本不存在,所以一般加锁无法屏蔽这类问题;
隔离级别
读未提交【Read Uncommitted】:在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果(实际生产中不可能使用这种隔离级别的),这种情况相当于没有任何隔离性,会有很多并发问题,如脏读,幻读,不可重复读等; 读已提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义——一个事务只能看到其他的已经提交的事务所做的改变,这种隔离级别会引起不可重复读,即一个事务执行时,如果多次select
,可能得到不同的结果; 可重复读 【Repeatable Read】:这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行,具体做法是在当前事务提交后,才会读取到别的事务所提交的操作,但是会有幻读问题;串行化【Serializable】:这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题,它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用); 下表为各种隔离级别所对应的状态:y——会发生该问题,n——不会发生该问题;
隔离级别 脏读 不可重复读 幻读 加读锁 读未提交 y y y 不加锁 读已提交 n y y 不加锁 可重复读 n n n 不加锁 串行化 n n n 加锁
一致性
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库处于一致性状态,如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态,因此一致性是通过原子性来保证的; 一致性和用户的业务逻辑强相关,一般 MySQL 提供技术支持,但是一致性还是需要用户业务逻辑做支撑,所以一致性是由用户决定的;
MVCC介绍
概念
概念:多版本并发控制( MVCC )是一种用来解决读-写冲突的无锁并发控制,为事务分配单向增长的事务 ID,为每个修改保存一个版本,版本与事务 ID 关联,读操作只读该事务开始前的数据库的快照,所以 MVCC 可以为数据库解决以下问题:
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能; 可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题;
前置知识
三个记录隐藏字段:
DB_TRX_ID
:6 byte,最近修改(修改 / 插入)事务 ID,记录创建这条记录 / 最后一次修改该记录的事务 ID;DB_ROLL_PTR
:7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就行,这些数据一般在undo log
中);DB_ROW_ID
:6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID
产生一个聚簇索引;补充:实际还有一个删除flag
隐藏字段,即记录被更新或删除并不代表真的删除,而是删除flag
变了; undo log
日志: MySQL 是以服务进程的方式,在内存中运行,之前所讲的所有机制:索引、事务、隔离性、日志等,都是在内存中完成的,即在 MySQL 内部的相关缓冲区中,保存相关数据,完成各种判断操作,然后在合适的时候,将相关数据刷新到磁盘当中的,所以,undo log
简单理解就是 MySQL 中的一段内存缓冲区,用来保存日志数据;
经过上面的操作,我们就有了一个基于链表记录的历史版本链,所谓的回滚,无非就是用历史数据,覆盖当前数据,上面的一个一个版本,我们可以称之为一个一个的快照; 上面是以更新(upadte
)为例的,如果是delete
呢?其实是差不多的,因为删数据不是清空数据,而是设置 flag 为删除即可,也可以形成版本; 如果是insert
呢?因为是插入,也就是说之前没有数据,那么也就没有历史版本,但是一般为了回滚操作,插入的数据也是要被放入 undo log
中,如果当前事务commit
了,那么这个undo log
的历史insert
记录就可以被清空了; 总结一下,也就是我们可以理解成,update
和delete
可以形成版本链,insert
暂时不考虑; 那么select
呢?首先,查询不会对数据做任何修改,所以为select
维护多版本没有意义,但是有一个问题,select
读取,是读取最新的版本呢?还是读取历史版本?
当前读:读取最新的记录,就是当前读,增、删、改都是当前读,select
也有可能当前读; 快照读:读取历史版本(一般而言),就叫做快照读; 在多个事务同时增、删、改的时候,都是当前读,是要加锁的,那同时有select
过来时,如果也要当前读,那么也就需要加锁,这就是串行化,但如果是快照读,读取历史版本的话,是不受加锁限制的,也就可以并行执行,提高了效率,这就是 MVCC 的意义所在;
Read View
:它的主要功能是让不同的事务,看到它应该看到的内容,Read View
是事务进行快照读操作时所产生的读视图,它作为条件,用来判断当前事务能够看到哪个版本的数据,它的主要成员如下:
m_ids;
up_limit_id;
low_limit_id;
creator_trx_id