事务是访问数据库的一个操做序列,数据库应用系统经过事务集来完成对数据库的存取.mysql
事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)的缩写,这四种状态的意思是:
1.原子性(Atomicity)
原子性是指事务包含的全部操做要么所有成功,要么所有失败回滚,这和前面两篇博客介绍事务的功能是同样的概念,所以事务的操做若是成功就必需要彻底应用到数据库,若是操做失败则不能对数据库有任何影响。
2.一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另外一个一致性状态.
3.隔离性(Isolation)
在事务正确提交以前,不容许把事务对该数据的改变提供给任何其余事务,即在事务正确提交以前,它可能的结果不该该显示给其余事务.
4.持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使是在数据库系统遇到故障的状况下也不会丢失提交事务的操做。算法
当多个线程都开启事务操做数据库中的数据时,数据库系统要能进行隔离操做,以保证各个线程获取数据的准确性.sql
1.第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了.
2.第二类丢失更新:A事务覆盖B事务已经提交的数据,形成B事务所作操做丢失.
3.脏读:A事务读取了事务B中未提交的数据.
4.不可重复读:A事务屡次读取的值不一样,由于该值被B事务修改并提交了.
5.幻读:A事务两次读之间,B事务插入了数据.数据库
为了解决上面的问题,开发者为MySQL数据库设计了如下四种事务隔离级别:
1.Read Uncommitted(未提交读):容许脏读,也就是可能读取到其余会话中未提交事务修改的数据.数组
2.Read Committed(提交读):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读).session
3.Repeated Read(可重复读):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,可是还存在幻象读,可是innoDB解决了幻读.并发
4.Serializable(串行读):彻底串行化的读,每次读都须要得到表级共享锁,读写相互都会阻塞.数据库设计
隔离级别 | 脏读 | 不可重复度 | 不幻读 |
---|---|---|---|
Read Uncommitted(未提交读) | 可能 | 可能 | 可能 |
Read Committed(提交读) | 不可能 | 可能 | 可能 |
Repeated Read(可重复读) | 不可能 | 不可能 | 可能 |
Serializable(串行读) | 不可能 | 不可能 | 不可能 |
1.查看全局或会话的事务隔离级别函数
SELECT @@global.tx_isolation, @@tx_isolation;
2.修改全局或会话的事务隔离级别源码分析
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
如下将先介绍数据库所涉及的锁.
1.锁简介
数据库中的锁是指一种软件机制,用来控制防止某个用户(进程会话)在已经占用了某种数据资源时,其余用户作出影响本用户数据操做或致使数据非完整性和非一致性问题发生的手段。
2.锁的级别
按照锁级别划分,锁可分为共享锁、排他锁。
针对同一块数据,多个读操做能够同时进行而不会互相影响。共享锁只针对UPDATE时候加锁,在未对UPDATE操做提交以前,其余事务只可以获取最新的记录但不可以UPDATE操做。
当前写操做没有完成前,阻断其余写锁和读锁。
3.锁的粒度
按锁的粒度划分,锁可分为表级锁、行级锁、页级锁。
开销大,加锁慢,会出现死锁,锁定力度最小,发生锁冲突的几率最低,并发度高。
开销小,加锁快,不会出现死锁,锁定力度大,发生冲突所的几率高,并发度低。
开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定力度介于表和行行级锁之间,并发度通常。
1.基本思想:老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁.因此无论冲突是否真的发生,都会使用锁机制。
2.悲观锁功能:
1.基本思想:老是假设最好的状况,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样能够提升吞吐量.
2.解释:乐观锁是一种思想,乐观锁不会锁住任何东西,也就是说,它不依赖数据库的事务机制,乐观锁彻底是应用系统层面的东西。因此它不是一种锁机制.若是使用乐观锁,那么数据库就必须加版本字段,不然就只能比较全部字段,但由于浮点类型不能比较,因此实际上没有版本字段是不可行的
通常是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,不然重试更新操做,直到更新成功。
1.核心思想:Compare and Swap,即比较再交换。
2.过程:假设有A线程准备去修改内存中变量名为name的值,所以A线程会用之前本身读到的name变量值和此刻name的值作对比,若是同样,则代表在变量值没被修改过,所以能够更新修改,不然更新失败.
前面说过,MySQL默认实现了可重复读的事务隔离级别,可是不能解决幻读的问题,然而在MySQL数据库使用可重复读的事务隔离条件下,并未发生幻读.MySQL使用MVCC(多版本并发控制)进行了控制.
1.MVCC:是multiversion concurrency control的简称,也就是多版本并发控制,是个很基本的概念。MVCC的做用是让事务在并行发生时,在必定隔离级别前提下,能够保证在某个事务中能实现一致性读,也就是该事务启动时根据某个条件读取到的数据,直到事务结束时,再次执行相同条件,仍是读到同一份数据,不会发生变化(不会看到被其余并行事务修改的数据)。
2.read view:InnoDB MVCC使用的内部快照的意思。在不一样的隔离级别下,事务启动时(有些状况下,多是SQL语句开始时)看到的数据快照版本可能也不一样。在上面介绍的几个隔离级别下会用到 read view。
3.快照读: 就是所谓的根据read view去获取信息和数据,不会加任何的锁。
4.当前读:前读会获取获得全部已经提交数据,按照逻辑上来说的话,在一个事务中第一次当前读和第二次当前读的中间有新的事务进行DML操做,这个时候俩次当前读的结果应该是不一致的,可是实际的状况倒是在当前读的这个事务还没提交以前,全部针对当前读的数据修改和插入都会被阻塞,主要是由于next-key lock解决了当前读可能会发生幻读的状况。
next-key lock当使用主键索引进行当前读的时候,会降级为record lock(行锁)
InnoDB支持MVCC多版本控制,其中READ COMMITTED和REPEATABLE READ隔离级别是利用consistent read view(一致读视图)方式支持的。所谓的consistent read view就是在某一时刻给事务系统trx_sys打snapshot(快照),把当时的trx_sys状态(包括活跃读写事务数组)记下来,以后的全部读操做根据其事务ID(即trx_id)与snapshot中trx_sys的状态作比较,以此判断read view对事务的可见性。
REPEATABLE READ隔离级别(除了GAP锁以外)和READ COMMITTED隔离级别的差异是建立snapshot时机不一样。REPEATABLE READ隔离级别是在事务开始时刻,确切的说是第一个读操做建立read view的时候,READ COMMITTED隔离级别是在语句开始时刻建立read view的。这就意味着REPEATABLE READ隔离级别下面一个事务的SELECT操做只会获取一个read view,可是READ COMMITTED隔离级别下一个事务是能够获取多个read view的。
建立/关闭read view须要持有trx_sys->mutex,会下降系统性能,5.7版本对此进行优化,在事务提交时session会cache只读事务的read view。
在InnoDB中,建立一个新事务的时候,InnoDB会将当前系统中的活跃事务列表(trx_sys->trx_list)建立一个副本(read view),副本中保存的是系统当前不该该被本事务看到的其余事务id列表。当用户在这个事务中要读取该行记录的时候,InnoDB会将该行当前的版本号与该read view进行比较。
具体的算法以下:
设该行的当前事务id为trx_id,read view中最先的事务id为trx_id_min, 最迟的事务id为trx_id_max。
若是trx_id< trx_id_min的话,那么代表该行记录所在的事务已经在本次新事务建立以前就提交了,因此该行记录的当前值是可见的。
若是trx_id>trx_id_max的话,那么代表该行记录所在的事务在本次新事务建立以后才开启,因此该行记录的当前值不可见。
若是trx_id_min <= trx_id <= trx_id_max, 那么代表该行记录所在事务在本次新事务建立的时候处于活动状态,从trx_id_min到trx_id_max进行遍历,若是trx_id等于他们之中的某个事务id的话,那么不可见,如图:
从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号的数据,将该可见行的值返回。
须要注意的是,新建事务(当前事务)与正在内存中commit 的事务不在活跃事务链表中。
在具体多版本控制中咱们先来看下源码:
函数:read_view_sees_trx_id。 read_view中保存了当前全局的事务的范围: 【low_limit_id, up_limit_id】 1.当行记录的事务ID小于当前系统的最小活动id,就是可见的。 if (trx_id < view->up_limit_id) { return(TRUE); } 2.当行记录的事务ID大于当前系统的最大活动id(也就是还没有分配的下一个事务的id),就是不可见的。 if (trx_id >= view->low_limit_id) { return(FALSE); } 3.当行记录的事务ID在活动范围之中时,判断是否在活动链表中,若是在就不可见,若是不在就是可见的。 for (i = 0; i < n_ids; i++) { trx_id_t view_trx_id = read_view_get_nth_trx_id(view, n_ids - i - 1); if (trx_id <= view_trx_id) { return(trx_id != view_trx_id); } }
Read view 图解:
笔者水平有限,文中若有不妥,请你们多多指教,MySQL数据库事务机制还有不少须要深刻研究的,咱们仍需不断钻研。
参考引用MySQL源码分析