MySQL(InnoDB剖析):---锁之(自增长与锁、外键和锁)

一、自增长与锁

  • 自增长在数据库中是非常常见的一种属性,也是开发人员首选的主键方式

自增长计数器

  • 在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存储引擎》

猜你喜欢

转载自blog.csdn.net/m0_46405589/article/details/113737485