开发多用户、数据库驱动的应用时,最大的一个难点是:一方面要最大程度地利用数据库地并发访问,另一方面还要确保每一个用户能以一致地方式读取和修改数据。算法
锁机制用于管理对共享资源地并发访问。InnoDB存储引擎会在行级别上对表数据上锁。数据库
latch是一种轻量级地锁,分为mutex(互斥量)和rwlock(读写锁)。其目的是用来保证并发线程操做临界资源地正确性,而且一般没有死锁检测机制。并发
lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。lock的对象仅在事务commit或rollback后进行释放。另外,lock有死锁机制。测试
3.1 锁的类型线程
InnoDB存储引擎还支持一种额外的锁方式,即意向锁(Intension lock)。意向锁将锁定的对象分为多个层次,意向锁意味着事务但愿在更细粒度上进行加锁。 3d
若将上锁的对象当作一个树,那么对对下层的对象上锁(最细粒度的对象上锁),须要首先对粗粒度的对象上锁。以下图所示,若是须要对页上的记录r上X锁,须要对数据库A、表、页上意向锁IX,最后对记录r上X锁。版本控制
锁的兼容性以下图所示。对象
3.2 一致性非锁定读blog
一致性非锁定读(consistent nonlocking read)指InnoDB存储引擎经过行多版本控制的方式来读取当前执行时间数据库中的行数据。若是读取的行正在执行DELETE或UPDATE操做,这时,读取操做不会等待行上锁的释放,InnoDB存储引擎会去读取行的一个快照数据。非锁定读机制能够极大的提升数据库的并发性。 索引
读取的快照数据来自undo段,undo用来在事务中回滚数据,所以快照数据自己没有额外的开销。由图中能够看到,一个行记录可能有不止一个快照数据,通常称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(multi version concurrency control MVCC)。
在READ COMMITED 事务隔离级别下,对于快照数据,一致性非锁定读老是读取非锁定行的最新一份快照数据。在REPEATABLE READ 事务隔离级别下,对于快照数据,一致性非锁定读老是读取事务开始时的行数据版本。
如上表所示执行,在时间点5,两种隔离模式,获得的结果同样,即id = 1; 在时间点7,两种隔离模式,会获得不一样的结果,READ COMMITED 获得 Empty Set (读取最新的行数据快照), REPEATABLE READ还是id =1(事务开始时的行数据)。
3.3 一致性锁定读
在某些状况下,用户须要显式地对数据库读取操做进行加锁以保证数据逻辑的一致性。InnoDB存储引擎对于SELECT语句支持两种一致性地锁定读(locking read)操做:
此外,这两种操做必须在一个事务中,当事务提交了,锁也就释放了。所以,在使用上述两句SELECT锁定语句时,务必加上BEGIN, START TRANSACTION 或者SET AUTOCOMMIT=0。
3.4 外键和锁
对于外键地插入或更新,首先须要查询父表中的记录,即SELECT父表。可是对于父表的SELECT操做,不是使用一致性非锁定读的方式,由于这样会发生数据不一致的问题。这时,使用的是SELECT...LOCK IN SHARE MODE方式,即主动对父表加一个S锁。若是这时父表上已经有了X锁,子表上的操做会被阻塞,以下表所示。
4.1 行锁的三种算法
InnoDB存储引擎有三种行锁的算法:
InnoDB对行的查询都是采用Next-Key-Lock算法,该算法能够解决Phantom Problem,假如一个索引有10,11,13,20这四个值,那么被索引的区间为:(-∞, 10], (10, 11], (11, 13], (13, 20], (20, +∞)
当查询的列是惟一索引时,会降级为Record Lock,如果辅助索引,状况会不太同样,先建立以下测试表z:
如今会话A中执行上面的SQL语句,因为b列是辅助索引,Next-Key-Lock算法会锁定(1,3] ,另外,特别须要注意的是,InnoDB存储引擎还会对辅助索引下个键值(即6)加上gap lock,因此锁定的辅助索引为1 2 3 4 5,因此运行下面的SQL语句都会被阻塞。
而下面的SQL语句则不会被阻塞:
4.2 解决Phantom Problem
在默认的事务隔离级别下,即REPEATABLE READ下,InnoDB存储引擎采用Next-Key-Locking机制来避免Phantom Problem(幻像问题)。
Phantom Problem是指在同一事务下,连续执行两次一样的SQL语句可能致使不一样的结果,第二次的SQL语句可能返回以前不存在的行。
假设表由一、二、5三个值组成。若执行以下的SQL语句:
会话A在时间3 和 7 执行的SQL语句会获得不一样的结果。为了不Phantom Problem,对于上述SQL语句,其锁住的不是5这个值,而是对(2,∞)这个范围加了X锁。所以,对于这个范围的插入都是不被容许的,从而避免了Phantom Problem。
5.1 脏读
脏数据是指事务对缓冲池中行记录进行了修改,可是尚未提交的数据。若是读到了脏数据,即一个事务能够读到另一个事务中未提交的数据,则显然违反了数据库的隔离性(脏读)。下表是一个脏读的例子。
READ UNCOMMITTED能够应用在一些比较特殊的状况。例如,replication环境中的slave节点,而且在该slave上的查询并不须要特别精确的返回值
5.2 不可重复读
不可重复读是指在一个事务内屡次读取同一数据集合。在这个事务尚未结束时,另一个事务也访问同一数据集合,并作了一些DML操做。所以,在第一个事务中的两次读数据之间,因为第二个事务的修改,那么第一个事务两次读到的数据多是不同的,即一个事务内两次读到的数据是不同的,即不可重复读。不可重复读的示例以下表所示。
InnoDB存储引擎的默认事务隔离级别是READ REPEATABLE,采用Next-Key-Lock算法,避免了不可重复读的现象。
5.3 丢失更新
一个事务的更新操做会被另外一个事务的更新操做所覆盖,从而致使数据的不一致。出现下面的状况时,就会发生丢失更新:
1) 事务T1查询一行数据,放入本地内存,并显示给一个终端用户User1
2) 事务T2也查询该行数据,并将取得的数据显示给终端用户User2
3) User1修改该行记录,更新数据库并提交
4) User2修改该行记录,更新并提交数据
要避免丢失更新发生,须要事务在这种状况下的操做变成串行化,而不是并行的操做。以下表所示:
由于不一样锁之间的兼容性关系,在有些时刻一个事务中的锁须要等待另外一个事务中的锁释放它所占用的资源,这就是阻塞。
在InnoDB存储引擎中,参数innodb_lock_wait_timeout用来控制等待的时间(默认50s,动态参数,能够在运行时调整),innodb_on_timeout(静态参数,不可在启动后,修改)用来设定是否在等待超时时,对进行中的事务进行回滚操做(默认是OFF,表明不回滚)。当作默认设置时,可能存在以下问题:
由以上代码可知,事务B因为等待事务A释放a<4的锁资源发生了超时,虽然没有进行COMMIT操做,可是数值5仍是插入到了数据库中。这是十分危险的状态,用户必须判断是否须要COMMIT仍是ROLLBACK,而后再进行下一步操做。
当两个或两个以上的事务在执行过程当中,因争夺锁资源而形成的一种互相等待的现象。
解决死锁问题最简单的方法就是超时回滚(当一个等待时间超过阈值,进行回滚)。
数据库通常采用wait-for graph(等待图)的方式来进行死锁检测,须要保存如下两种信息:
在Transaction list 中能够看到共有四个事务t1, t2, t3, t4.
存在t1 t2的回路,故而存在死锁。InnoDB存储引擎通常选择回滚undo量最小的事务。
锁升级是指将当前锁的粒度下降。即把一个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁。
InnoDB不存在锁升级问题。其根据每一个事务访问的每一个页对锁进行管理,采用位图的方式。所以,无论一个事务锁住页中一个记录仍是多个记录,开销一般是一致的。
假设一张表有3 000 000个数据页,每一个页大约有100条记录,总共有300 000 000条记录。若一个事务更新全表更新语句,须要对全部记录加X锁。若根据每行记录产生锁对象,假设每一个锁10字节,则锁管理须要3GB内存。
而InnoDB存储引擎根据页进行加锁,每一个页的锁信息占30个字节,则锁对象仅需90MB内存。