对 MySQL 来讲,事务一般是一组包含对数据库操做的集合。在执行时,只有在该组内的事务都执行成功,这个事务才算执行成功,不然就算失败。MySQL 中,事务支持是在引擎层实现的,像 MySQL 原生的 MyISAM 引擎就不支持事务,这也是被 InooDB 取代的重要缘由。mysql
为何要有事务呢,举个例子来讲,你的帐户有 100 元,如今想给朋友转帐 100 元。其中就会包含两个很重要的操做,你的帐户减 100 元,朋友帐户多 100 元。因为转帐过程当中出现失败是很常见的,假设操做不包含在事务内,你的帐户减钱操做成功,朋友帐户加钱操做失败。就会出现,你的帐户扣钱,对方没有收到钱的状况。sql
再好比,在发起转帐操做时,因为系统须要进行像查询余额,计算,更新余额的操做,若是在等待时间内,又发起了转帐操做,但目前更新余额的操做尚未成功,就会出现你的 100 元,能够给别人转帐屡次的状况,这对于银行来讲是确定不容许的。数据库
对于一个事务来讲一般要知足四个特性,也就是一般所说的 ACID:并发
Atomicity - 保证在一个工做单元(就是一组操做)中全部的操做都执行成功,不然的话当前这个事务就会失败,以前的操做都被会回滚。框架
Consistency - 保证一个事务被成功提交后,数据库的状态是从一致性状态变成另外一个一致性状态。性能
Isolation - 保证每一个事务中的操做时是独立的,对于其余事务没有影响。日志
Durability - 对于已经提交的事务,即便在数据库损坏的状况下,也不会形成数据的丢失和损坏。code
当数据库中有多个事务同时执行时,就可能会出现脏读,幻读,不可重复读的问题,为了解决这些问题,就出现了"隔离级别"的概念。orm
脏读:事务 A 中访问了事务 B 中未提交的数据。这里 Transaction 1 读到了,Transaction 2 中未提交的年龄数据。blog
不可重复读:事务 A 中屡次查询同一数据,但因为事务 B 在事务 A 两次查询中,修改了改数据的值,致使两次查询的结果不同。下面 Transaction 1 中的两次查询查询结果是一致的,第二次读到的 age 已经被修改的内容。
幻读:一般发生在事务 B 对事务 A 正在读取的内容,添加或删除了一条数据。形成数据莫名出现或者消失的状况。这里 Transaction 1 中,两次查询的结果并不一致,第二次查询会多出一条记录。
隔离级别 | 解释 | 可能出现的问题 |
---|---|---|
读未提交 | 读未提交是指,一个事务还没提交时,它作的变动就能被别的事务看到。 | 脏读,不可重复读,幻读 |
读提交 | 读提交是指,一个事务提交以后,它作的变动才会被其余事务看到。 | 不可重复读,幻读 |
可重复读 | 可重复读是指,一个事务执行过程当中看到的数据,老是跟这个事务在启动时看到的数据是一致的。固然在可重复读隔离级别下,未提交变动对其余事务也是不可见的。 | 幻读 |
串行化 | 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 | 无 |
在 MySQL 中 RR 级别引入了间隙锁,解决了幻读的问题。
举一个实际的例子,来看一下这四种隔离级别对应的结果,假设表结构以下:
mysql> create table T(c int) engine=InnoDB; insert into T(c) values(1);
此时发生的事务以下:
隔离级别 | 返回结果 |
---|---|
读未提交 | V1 是 2。事务 B 虽然尚未提交,可是结果已经被 A 看到了。所以,V二、V3 也都是 2 |
读提交 | V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。因此, V3 的值也是 2。 |
可重复读 | 则 V一、V2 是 1,V3 是 2。 V2 仍是 1 的缘由,须要遵循:事务在执行期间看到的数据先后必须是一致的。 |
串行化 | 事务 B 执行“将 1 改为 2”时,会被锁住。直到事务 A 提交后,事务 B 才能够继续执行。因此从 A 的角度看, V一、V2 值是 1,V3 的值是 2。 |
执行的效率会和执行的级别有关,隔离的越高,效率越低,须要在两者间寻找平衡。
读未提交:
读提交:
可重复读:
串行化:
在实现上,数据库里面会建立一个视图,访问的时候以视图的逻辑结果为准。
注意这个视图不是用于查询定义的虚拟表,而是在 InnoDB 中实现 MVCC 用到的一致性读视图(consistent read view),用于支持 RC 和 RR 隔离级别的实现。
可重复读的具体实现:
在 MySQL 中,实际上每条记录在更新时都会同时记录一条回滚操做。记录上的最新值,经过回滚均可以获得前一个状态的值。好比一个值 从 1 按照顺序,被修改为 二、三、4 ,就会在回滚日志中有以下的记录。
当前最新是 4,在查询这条记录时,不一样时刻启动的事务会有不一样的 read-view. 在视图 A B C 中,记录值为 1, 2, 4. 同一条记录能够存在多个版本,这就是数据库多版本并发控制(MVCC)。对于 read-view A 来讲,要获得 1,就必须将当前值依次执行图中全部的回滚操做获得。假如,有另一个事务将 4 改为 5,但对于视图 A B C 来讲,事务是不冲突的。
既然每一条记录都会更新是都会产生一条回滚操做记录,时间一长,确定会占用大量的存储空间。那么系统会在何时删除这些回滚日志呢,就是在当前系统里不存在比该回滚日志更早的 read-view 时。
但若是系统里存在着很老的事务视图。因为这些事务可能会访问数据库里的任何数据,因此在事务提交以前,全部可能用到的回滚记录都必须保留,这就可能出现占用大量存储空间的状况。
在 MySQL 5.5 以前,回滚日志和数据字典一块儿放在 ibdata 文件里,即便长事务被提交,回滚段被清理,文件也不会变小。
而且长事务还占用锁资源,也可能拖垮整个库。
显式启动事务:
# 使用 START TRANSACTION 或者 BEGIN 开启事务: START TRANSACTION [transaction_characteristic [, transaction_characteristic] ...] transaction_characteristic: { WITH CONSISTENT SNAPSHOT | READ WRITE | READ ONLY } BEGIN [WORK] # 使用 COMMIT 来提交事务 COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] # 使用 ROLLBACK 来回滚事务 ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
隐式启动事务:
# 设置当前事务的是否自动提交 SET autocommit = {0 | 1}
autocommit 的讨论:
commit work and chain
语法。多一次交互的问题,若是采用 autocommit=0 的这种方式,不须要每次输入 begin ,减小了语句的交互次数。
查询长事务:
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
避免长事务的方案:
在开始部分,介绍了 MySQL 中事务的概念,并回顾了事务的 ACID 的特性。接着探讨了事务隔离的可能出现的脏读,不可重复读以及幻读的问题,并给出了相应的解决方案-隔离级别。并分析了常见事务隔离的应用场景以及事务隔离的实现方式。
并在最后引出了回滚段的概念,以及为何要避免使用长事务。并给出了开启事务的方法。