一、自增长与锁
- 自增长在数据库中是非常常见的一种属性,也是开发人员首选的主键方式
自增长计数器
- 在InnoDB的内存结构中,对每个含有自增长值的表都有一个自增长计数器(auto-increment counter),当对含有自增长的计数器的表进行插入操作时,这个计数器会被初始化
- 执行如下的语句可以得到计数器的值
select max(auto_inc_col) from t for update;
自增长与锁(AUTO-INC Locking)
- 插入操作会依据这个自增长的计数器值加1赋值给自增长列。这个实现方式称作AUTO-INC Locking
- 这种锁其实是采用一种特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后才释放,而是在完成对自增长值插入的SQL语句后立即释放
- 虽然AUTO-INC Locking从一定程度上提高了并发插入的效率,但是还存在一些性能上的问题:
- 首先,对于有自增长值的列的并发插入性能较差,事务必须等待前一个插入的完成(虽然不用等待事务的完成)
- 其次,对于INSERT...SELECT的大数据量的插入会影响插入的性能,因为另一个事务中的插入会被阻塞
innodb_autoinc_lock_mode参数
- 从MySQL 5.1.22开始,InnoDB提供了一种轻量级互斥量的自增长实现机制,这种机制大大提高了自增长值插入的性能
- 并且从该版本开始,InnoDB存储引擎提供了此参数来控制自增长的模式。该参数的默认值为1
- 介绍自增长实现方式之前,需要对自增长的插入类型进行分类,如下表所示:
- 该参数共有3个有效值(0、1、2),具体说明如下:
InnoDB与MyIASM的不同之处
此外,还需要注意的是InnoDB中自增长的实现与MyIASM不同,MyIASM是表锁设计,自增长不用考虑并发插入的问题
因此在master上用InnoDB存储引擎,在slave上用MyIASM存储引擎的replication架构下,用户必须考虑这两种情况
另外,在InnoDB中,自增长的列必须是索引,同时必须是索引的第一个列。如果不是第一个列,MySQL会抛出异常,而MyIASM存储引擎没有这个限制。如下图所示
create table t( a int auto_increment, b int, key(b,a) )engine=InnoDB;
create table t2( a int auto_increment, b int, key(b,a) )engine=MyISAM;
二、外键和锁
- 外键主要用于引用完整性的约束检查
- 在InnoDB中,对于一个外键列,如果没有显示地对这个列加索引,InnoDB会自动对其加一个索引,因为这样可以避免加锁——这比Oracle数据库做得好,Oracle数据库不会自动添加索引,用户必须手动添加,这也导致了Oracle可能产生死锁
外键和锁的工作原理
- 对于外键值的插入或更新,首先需要查询父表中的记录,即SELECT父表
- 但是对于父表的SELECT操作,不是使用一致性非锁定读的方式,因为这样会发生数据不一致的问题,因此这时使用的是SELECT...LOCK IN SHARE MODE方式,即主动对父表加一个S锁。如果这时父表上已经有X锁,子表上的操作会被阻塞
演示案例
- 创建一个父表parent,向其中插入3条记录
create table parent( id int primary key ); insert into parent select 1; insert into parent select 2; insert into parent select 3; select * from parent;
- 创建一个子表child。其中子表的第二个字段为外键,指向于parent表的id字段
create table child( child_id int primary key, parent_id int not null, foreign key(parent_id) references parent(id) ); insert into child select 1,1; select * from child;
- 现在开启会话A,在会话A中删除parent表中id为3的记录(此时在id为3的记录上加了一个X锁),但是事务不提交:
begin; delete from parent where id=3;
- 此时开启一个会话B,想在会话B中向child表中插入一条语句(加S锁),由于第二个字段是外键,那么这条语句会被阻塞(因为parent中id为3的字段已经加了X锁),从图片中可以看到insert语句被阻塞
begin; insert into child select 2,3;
- 设想一些(只是假想,不是真的):如果使用的是一致性的非锁定读,这时Session B会读到父表有id=3的记录。数据在父子表就会存在不一致的情况
- 现在会话C中查询Innodb_locks表,会看到如下结果:
select * from information_schema.innodb_locks\G
- 过了一会之后,我们再次查看会话B,可以看到等待锁的时间超时了,这条语句也就插入失败了
- 整个过程如下图所示:
- 备注:当我们的会话A提交之后,会话B的插入语句也会失败,因为会话A已经将parent表中id为3的字段删除了,因此会话B插入会失败,这样保证了父子表之间的数据完整性与一致性
内容摘自《Mysql技术内幕---InnoDB存储引擎》