全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句DDL语句,已经更新操作的事务提交语句都将被阻塞。用于全库的逻辑备份,对所有的表进行锁定

1
2
3
4
5
6
7
8
# 加全局锁
flush tables with read lock;

# 数据备份
mysqldump -uroot -p 123456 itcast > itcast.sql ;

# 释放锁
unlock tables;

数据库中加全局锁,是一个比较重的操作,存在以下问题:

如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导
致主从延迟。

在InnoDB引擎中,我们可以在备份时加上参数 —single-transaction 参数来完成不加锁的一致
性数据备

1
mysqldump --single-transaction -u root –p 123456 itcast > itcast.sql ;

表级锁

表锁

表共享读锁

DQL可以,DDL/DML不可以

不会影响其他客户端的读,但是会阻塞其他客户端的写

表独占写锁

DQL可以,DDL/DML可以 另一个客户端 全不可以

对指定表加了写锁,会阻塞其他客户端的读和写

1
2
3
lock tables 表名 read/write ;

unlock tables ;

元数据锁

MDL加锁过程是系统自动控制,无需显式使用。在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。

1
meta data lock ;
对应SQL 锁类型 说明
lock tables xxx read / write SHARED_READ_ONLY /SHARED_NO_READ_WRITE
select 、select …
lock in share mode
SHARED_READ 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert 、update 、 delete、select … for update SHARED_WRITE 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
alter table … EXCLUSIVE 与其他的MDL都互斥
1
2
# 通过该SQL语句,来查看元数据锁的加锁情况
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;

意向锁

为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查

假如没有意向锁,客户端一对表加了行锁后,客户端二如何给表加表锁呢

首先客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。

当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就
会从第一行数据,检查到最后一行数据,效率较低。

有了意向锁之后 :客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。

而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而
不用逐行判断行锁情况了。

意向共享锁(IS): 由语句select … lock in share mode添加 。 与 表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
意向排他锁(IX): 由insert、update、delete、select…for update添加 。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。

一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。

1
2
# 可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

行级锁

介绍

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中

InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在
RC(read commit)、RR(repeatable read)隔离级别下都支持。

间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事
务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的所有间隙Gap。
在RR隔离级别下支持。

行锁

InnoDB实现了以下两种类型的行锁:

共享锁(S):允许一个事务去一行,阻止其他事务获得相同数据集的排它锁。(共享锁和共享锁之间是兼容的,到那时和排他锁之间是互斥的)
排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

简单来说就是,我读的时候你们可以读(获取到了共享锁就可以读),但是我写的时候(获取到排他锁才可以写),你们既不可以读(阻止获得共享锁),也不可以写(组织其他事务获取排他锁)

S(共享锁) X(排他锁)
S(共享锁) 兼容 不兼容
X(排他锁) 不兼容 不兼容

常见的SQL语句,在执行时,所加的行锁如下:

查看意向锁及行锁的加锁情况:

1
2
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;

间隙锁&临键锁

默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜
索和索引扫描,以防止幻读。

  • 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。间隙锁的范围该记录的上下两个记录,(小,大],左闭右开
  • 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key
    lock 退化为间隙锁。
  • 索引上的范围查询(唯一索引)—会访问到不满足条件的第一个值为止。

注意:间隙锁唯一目的是防止其他事务插入间隙,造成幻读。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。

我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。 假如,我们要根据这个二级索引查询值
为18的数据,并加上共享锁,我们是只锁定18这一行就可以了吗? 并不是,因为是非唯一索引,这个
结构中可能有多个18的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也就是29)。此时会对18加临键锁,并对29之前的间隙加锁。

如果范围查找时:

查询的条件为id>=19,并添加共享锁。 此时我们可以根据数据库表中现有的数据,将数据分为三个部分:
[19]
(19,25]
(25,+∞]
所以数据库数据在加锁时,就是将19加了行锁,25的临键锁(包含25及25之前的间隙),正无穷的临键锁(正无穷及之前的间隙)。

InnoDB引擎

架构

下面逐一解释

内存结构

在左侧的内存结构中,主要分为这么四大块儿: Buffer Pool、Change Buffer、Adaptive
Hash Index、Log Buffer。 接下来介绍一下这四个部分。

Buffer Pool

详见Buffer Poll 详解

InnoDB存储引擎基于磁盘文件存储,访问物理硬盘和在内存中进行访问,速度相差很大,为了尽可能弥补这两者之间的I/O效率的差值,就需要把经常使用的数据加载到缓冲池中,避免每次访问都进行磁盘I/O。

在InnoDB的缓冲池中不仅缓存了索引页和数据页,还包含了undo页、插入缓存、自适应哈希索引以及InnoDB的锁信息等等。
缓冲池 Buffer Pool,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。

缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
• free page:空闲page,未被使用。
• clean page:被使用page,数据没有被修改过。
• dirty page:脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。

在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。

1
2
# 参数设置
show variables like 'innodb_buffer_pool_size';
Change Buffer

详见Change Buffer 详解

Change Buffer,更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。
Change Buffer的意义是什么呢?

先来看一幅图,这个是二级索引的结构图:

与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了Change Buffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。

如果索引设置了唯一(unique)属性,在进行修改操作时,InnoDB必须进行唯一性检查。也就是说,索引页即使不在缓冲池,磁盘上的页读取无法避免(否则怎么校验是否唯一?),此时就应该直接把相应的页放入缓冲池再进行修改,而不应该再整写缓冲这个幺蛾子。

Adaptive Hash Index

详见Adaptive Hash Index 详解

自适应hash索引,用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持hash索引,但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过,hash索引在进行等值匹配时,一般性能是要高于B+树的,因为hash索引一般只需要一次IO即可,而B+树,可能需要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。
InnoDB存储引擎会监控对表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。

自适应哈希索引,无需人工干预,是系统根据情况自动完成。
参数: adaptive_hash_index