在上一篇文章写了锁的基本概述以及行锁的三种形式,这一篇的主要内容以下:mysql
一致性非锁定读是InnoDB经过多版本并发控制(MVCC,multi version concurrency control)的方式来读取当前执行时间数据库中的最近一次快照,若是读取的行正在执行DELETE、UPDATE操做,这时读取操做不会等待行上锁的释放,相反,InnoDB存储引擎会去读取行的一个快照数据,以下图:sql
上图直观地展现了InnoDB存储引擎一致性的非锁定读,之因此称其为非锁定读,由于不须要等待访问的行上X锁的释放。快照数据是指该行以前版本的数据,该实现是经过Undo段来实现,而Undo用来在事务中回滚数据,所以快照数据自己是没有额外的开销。此外,读取快照数据是不须要上锁的,由于没有必要对历史的数据进行修改。数据库
能够看到,非锁定读的机制大大提升了数据读取的并发性,在InnoDB存储由于默认设置下,这是默认的读取方式,即读取不会占用和等待表上的锁。可是在不一样事务隔离级别下,读取的方式不一样,并非每一个事务隔离级别下读取的都是一致性读。一样,即便都是使用一致性读,可是对于快照数据的定义也不相同。架构
经过上图,咱们能够看出快照数据其实就是当前数据以前的历史版本,可能有多个版本。一个行可能又不止一个快照数据。咱们称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(MVCC,multi version concurrency control)并发
在READ COMMITTED和REPEATABLE READ下,InnoDB存储引擎使用非锁定的一致性读。然而,对于快照数据的定义却不相同。在READ COMMITTED事务隔离级别下,对于快照数据,非一致性读老是读取被锁定行的最新一份快照数据。在REPEATABLE READ事务隔离级别下,对于快照数据,非一致性读老是读取事务开始时的行数据版本。下面看一个列子:oracle
时间序列 | 会话A | 会话B |
1 | mysql> begin; #开启一个事务 Query OK, 0 rows affected (0.00 sec) mysql> select * from tb1 where a = 5; +---+ | a | +---+ | 5 | +---+ 1 row in set (0.00 sec) |
|
2 | mysql> begin; #开启一个事务B,更新同一条数据性能 |
|
3 | #这时候RR和RC隔离级别,查询到的数据都是以下(都解决了脏读问题):测试 mysql> select * from tb1 where a = 5; |
|
4 | #提交事务 mysql> commit; |
|
5 | #在RR的隔离级别下数据读到的数据以下:读取事务开始时的版本 mysql> select * from tb1 where a = 5; |
|
6 | #在RC的隔离级别下读到的数据以下:老是读取最新的一份快照数据。 mysql> select * from tb1 where a = 5; |
自增加在数据库中是很是常见的一种属性,也是不少DBA或开发人员首选的主键方式。在InnoDB存储引擎的内存结构中,对每一个含有自增加值的表都有一个自增加计数器。当对含有自增加的计数器的表进行插入操做时,这个计数器会被初始化,执行以下的语句来获得计数器的值:
select max(auto_inc_col) from t for update;
插入操做会依据这个自增加的计数器值加1赋予自增加列。这个实现方式称为AUTO-INC Locking。这种锁实际上是采用一种特殊的表锁机制,为了提升插入的性能,锁不是在一个事务完成后才释放,而是在完成对自增加值插入的SQL语句后当即释放。【注意自增锁释放的时机】
虽然AUTO-INC Locking从必定程度上提升了并发插入的效率,但仍是存在一些性能上的问题。首先,对于有自增加值的列的并发插入性能较差,事务必须等待前一个插入的完成,虽然不用等待事务的完成。其次,对于INSERT….SELECT的大数据的插入会影响插入的性能,由于另外一个事务中的插入会被阻塞。
从MySQL 5.1.22版本开始,InnoDB存储引擎中提供了一种轻量级互斥量的自增加实现机制,这种机制大大提升了自增加值插入的性能。而且从该版本开始,InnoDB存储引擎提供了一个参数innodb_autoinc_lock_mode来控制自增加的模式,该参数的默认值为1。在继续讨论新的自增加实现方式以前,须要对自增加的插入进行分类。以下说明:
接下来分析参数innodb_autoinc_lock_mode以及各个设置下对自增加的影响,其总共有三个有效值可供设定,即0、一、2,具体说明以下:
mysql> show variables like "innodb_autoinc_lock_mode"; #这个数值默认是1,而且是个只读的变量,不能改变,能够从源码改变 +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_autoinc_lock_mode | 1 | +--------------------------+-------+ 1 row in set (0.00 sec) mysql> set global innodb_autoinc_lock_mode = 2; ERROR 1238 (HY000): Variable 'innodb_autoinc_lock_mode' is a read only variable
这里须要特别注意,InnoDB跟MyISAM不一样,MyISAM存储引擎是表锁设计,自增加不用考虑并发插入的问题。所以在master上用InnoDB存储引擎,在slave上用MyISAM存储引擎的replication架构下,用户能够考虑这种状况。
另外,InnoDB存储引擎,自增持列必须是索引,同时必须是索引的第一个列,若是不是第一个列,会抛出异常,而MyiSAM不会有这个问题。
在给一个字段设置自增以后,从起始值开始,每次加1,那么这个起始值和步长是如下两个参数控制的:
mysql> show variables like "auto_increment%"; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | auto_increment_increment | 1 | #设置自增值的起始值 | auto_increment_offset | 1 | #设置自增值的步长 +--------------------------+-------+ 2 rows in set (0.00 sec)
mysql> set auto_increment_increment = 2; #设置起始值为2 Query OK, 0 rows affected (0.00 sec) mysql> set auto_increment_offset = 2; #设置步长为2 Query OK, 0 rows affected (0.00 sec) mysql> create table test1(id int auto_increment primary key, name varchar(20)); #建立表,插入测试数据 Query OK, 0 rows affected (0.05 sec) mysql> insert into test1(name) values("zhao"); Query OK, 1 row affected (0.00 sec) mysql> insert into test1(name) values("qian"); Query OK, 1 row affected (0.01 sec) mysql> insert into test1(name) values("sun"); Query OK, 1 row affected (0.00 sec) mysql> select * from test1; +----+------+ | id | name | +----+------+ | 2 | zhao | | 4 | qian | | 6 | sun | +----+------+ 3 rows in set (0.00 sec)
简单说一下外键,外键主要用于引用完整性的约束检查。在InnoDB存储引擎中,对于一个外键列,若是没有显示地对这个列加索引,InnoDB存储引擎会自动对其加一个索引,由于这样能够避免表锁。这比Oracle数据库作得好,Oracle数据库不会自动添加索引,用户必须本身手动添加,这也致使了Oracle数据库中可能产生死锁。
对于外键值的插入或更新,首先须要检查父表中的记录,既SELECT父表。可是对于父表的SELECT操做,不是使用一致性非锁定读的方式,由于这会发生数据不一致的问题,所以这时使用的是SELECT…LOCK IN SHARE MODE方式,即主动对父表加一个S锁。若是这时父表上已经这样加X锁,子表上的操做会被阻塞,以下:
实例以下:
# 建立parent表; create table parent( tag_id int primary key auto_increment not null, tag_name varchar(20) ); # 建立child表; create table child( article_id int primary key auto_increment not null, article_tag int(11), CONSTRAINT tag_at FOREIGN KEY (article_tag) REFERENCES parent(tag_id) ); # 插入数据; insert into parent(tag_name) values('mysql'); insert into parent(tag_name) values('oracle'); insert into parent(tag_name) values('mariadb');
开始测试
# Session A mysql> begin mysql> delete from parent where tag_id = 3; # Session B mysql> begin mysql> insert into child(article_id,article_tag) values(1,3); #阻塞
第二列是外键,执行该语句时被阻塞。
在上述的例子中,两个会话中的事务都没有进行COMMIT或ROLLBACK操做,而会话B的操做会被阻塞。这是由于tag_id为3的父表在会话中已经加了一个X锁,而此时在会话B中用户又须要对父表中tag_id为3的行加一个S锁,这时INSERT的操做会被阻塞。设想若是访问父表时,使用的是一致性的非锁定读,这时Session B会读到父表有tag_id=3的记录,能够进行插入操做。可是若是会话A对事务提交了,则父表中就不存在tag_id为3的记录。数据在父、子表就会存在不一致的状况。若这时用户查询INNODB_LOCKS表,会看到以下结果:
mysql> select * from information_schema.innodb_locks\G *************************** 1. row *************************** lock_id: 3359:35:3:4 lock_trx_id: 3359 lock_mode: S lock_type: RECORD lock_table: `test`.`parent` lock_index: PRIMARY lock_space: 35 lock_page: 3 lock_rec: 4 lock_data: 3 *************************** 2. row *************************** lock_id: 3358:35:3:4 lock_trx_id: 3358 lock_mode: X lock_type: RECORD lock_table: `test`.`parent` lock_index: PRIMARY lock_space: 35 lock_page: 3 lock_rec: 4 lock_data: 3 2 rows in set, 1 warning (0.00 sec)
从锁结构能够看出,对于parent表加了两个锁,一个S锁和一个X锁。
博文基本摘自inside君的《MySQL技术内幕--INNODB存储引擎》,实际地址来自:http://www.ywnds.com/?p=9129