MySQL的索引和锁
- 一.存储引擎
- 1.1 MySQL体系结构
- 1). 连接层
- 2). 服务层
- 3).存储引擎层
- 4). 存储层
- 1.2 存储引擎介绍
- 1). 建表时指定存储引擎
- 2). 查询当前数据库支持的存储引擎
- 1.3 存储引擎特点
- 1.3.1 InnoDB
- 1.3.2 MyISAM
- 1.4 存储引擎选择
- 二 索引
- 2.1 索引概述
- 2.1.1 介绍
- 2.2.2 B-Tree。
- 2.3 索引分类
- 2.3.1 索引分类
- 聚集索引:
- 二级索引:
- B树深度问题
- 2.4 索引语法
- 1). 创建索引
- 2). 查看索引
- 3). 删除索引
- 2.5 SQL性能分析
- 2.5.1 SQL执行频率
- 2.5.2 慢查询日志
- 2.5.3 profile详情
- 2.5.4 explain
- 2.6 索引使用
- 2.6.1 最左前缀法则
- 2.6.2 范围查询
- 2.6.3 索引失效情况
- 2.6.4 多个索引情况
- 2.6.5 覆盖索引
- 2.6.6 前缀索引
- 2.7 索引设计原则
- 三. SQL优化
- 3.1 插入数据
- 3.1.1 insert
- 3.2 主键优化
- 页分裂
- 页合并
- 索引设计原则
- 3.3 order by优化
- Using filesort:
- Using index :
- order by优化原则:
- 3.4 group by优化
- 3.5 limit优化
- 3.6 count优化
- 3.6.1 概述
- 3.6.2 count用法
- 3.7 update优化
- 四. 锁
- 4.1 概述
- 4.2 全局锁
- 4.2.1 介绍
- 4.2.2 语法
- 4.2.3 特点
- 4.3 表级锁
- 4.3.1 介绍
- 4.3.2 表锁
- 4.3.3 元数据锁
- 4.3.4 意向锁
- 4.4 行级锁
- 4.4.1 介绍
- 4.4.2 行锁
- 4.4.3 间隙锁&临键锁
一.存储引擎
1.1 MySQL体系结构
1). 连接层
最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。
2). 服务层
完成大多数的核心服务功能,如SQL接口,缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。
3).存储引擎层
负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库中的索引是在存储引擎层实现的。
4). 存储层
数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。
插件式的存储引擎架构,
1.2 存储引擎介绍
存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。
1). 建表时指定存储引擎
CREATE TABLE 表名(
字段1 字段1类型 [ COMMENT 字段1注释 ] ,
......
字段n 字段n类型 [COMMENT 字段n注释 ]
) ENGINE = INNODB [ COMMENT 表注释 ] ;
2). 查询当前数据库支持的存储引擎
show engines;
1.3 存储引擎特点
1.3.1 InnoDB
1)介绍
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的MySQL 存储引擎。
特点:
DML操作遵循ACID模型,支持事务;行级锁,提高并发访问性能;支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
3). 文件
innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm-早期的 、sdi-新版的)、数据和索引。
目录:
C:\ProgramData\MySQL\MySQL Server 8.0\Data
每一个ibd文件就对应一张表
4). 逻辑存储结构
表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以
包含多个Segment段。
段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管
理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为
16K, 即一个区中一共有64个连续的页。
页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默
认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时
所指定的字段以外,还包含两个隐藏字段。
1.3.2 MyISAM
1). 介绍
MyISAM是MySQL早期的默认存储引擎。
2). 特点
不支持事务,不支持外键
支持表锁,不支持行锁
访问速度快
3). 文件
xxx.sdi:存储表结构信息
xxx.MYD: 存储数据
xxx.MYI: 存储索引
1.3.3 Memory
1). 介绍
Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为
临时表或缓存使用。
2). 特点
内存存放
hash索引(默认)
3).文件
xxx.sdi:存储表结构信息
1.4 存储引擎选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据
实际情况选择多种存储引擎进行组合。
二 索引
2.1 索引概述
2.1.1 介绍
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
索引结构 | 描述 |
---|---|
B+Tree索引 | 最常见的索引类型,大部分引擎都支持 B+ 树索引 |
Hash索引 | 底层数据结构是用哈希表实现的, 只有精确匹配索引列的查询才有效, 不支持范围查询 |
R-tree(空间索引) | 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式。类似于ES |
注:这三个引擎都支持B+树
2.2.2 B-Tree。
B树是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。
以一颗最大度数(max-degree)为4(4阶)的b-tree为例,那这个B树每个节点最多存储3个key,4
个指针。
https://www.cs.usfca.edu/~galles/visualization/BTree.html
5阶的B树,每一个节点最多存储4个key,对应5个指针。
一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。
在B树中,非叶子节点和叶子节点都会存放数据。
2.2.3 B+Tree
所有的数据都会出现在叶子节点(没有分支的节点)。
叶子节点形成一个单向链表。
非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
MySQL优化后变成了双向链表
2.3 索引分类
2.3.1 索引分类
而在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
聚集索引:
必须有,而且只有一个(
如果存在主键,主键索引就是聚集索引。
如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。)
聚集索引的叶子节点下挂的是这一行的数据 。
二级索引:
索引结构的叶子节点关联的是对应的主键可以存在多个。
叶子节点下挂的是该字段值对应的主键值。(即查询可能要进行回表)。
B树深度问题
一行数据大小为1k,一页中可以存储16行这样的数据。InnoDB的指针占用6个字节的空
间,主键即使为bigint,占用字节数为8。
高度为2:
n * 8 + (n + 1) * 6 = 16*1024 , 算出n约为 1170
1171* 16 = 18736
也就是说,如果树的高度为2,则可以存储 18000 多条记录。
高度为3:
1171 * 1171 * 16 = 21939856
也就是说,如果树的高度为3,则可以存储 2200w 左右的记录。
2.4 索引语法
1). 创建索引
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (
index_col_name,... ) ;
2). 查看索引
SHOW INDEX FROM table_name ;
3). 删除索引
DROP INDEX index_name ON table_name ;
2.5 SQL性能分析
2.5.1 SQL执行频率
MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信
息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次。
查询增删改查次数
-- session 是查看当前会话 ;
-- global 是查询全局数据 ;
SHOW GLOBAL STATUS LIKE 'Com_______';
2.5.2 慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有
SQL语句的日志。
MySQL的慢查询日志默认没有开启,我们可以查看一下系统变量 slow_query_log。
show variables like 'slow_query_log%';
2.5.3 profile详情
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling
参数,能够看到当前MySQL是否支持profile操作:
是否支持
SELECT @@have_profiling ;
开启profile
set @@profiling=1;
之后所执行的SQL语句,都会被MySQL记录
select * from bookinfo b ;
-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
--show profile for query 1;
-- 查看指定query_id的SQL语句CPU的使用情况
--show profile cpu for query 1;
2.5.4 explain
EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行
过程中表如何连接和连接的顺序。
explain select * from xxl_job_log;
2.6 索引使用
2.6.1 最左前缀法则
前提:联合索引
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始(与书写顺序无关),
并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效,用explain可根据索引使用长度判断)。
2.6.2 范围查询
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。
字段A、B、C、联合索引 where A= and B> and C=
则A、B走了索引C没有走索引 。
注 当范围查询使用>= 或 <= 时,A、B、C都走联合索引了。
2.6.3 索引失效情况
注意联合索引的最左匹配
1)不要在索引列上进行运算操作(注意运算并只有加减,还有截取等), 索引将失效。
2)字符串类型字段使用时,不加引号,索引将失效。
3)如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
4)用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会
被用到。
5)如果MySQL评估使用索引比全表更慢,则不使用索引。(如索引重复占一大半)
6)is null 与 is not null 操作是否走索引 其实就是根据数据null数量才考虑是否走索引
2.6.4 多个索引情况
注:explain只是方便观看索引使用情况。
1). use index : 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进
行评估)。
explain select * from tb_user use index(idx_user_pro) where profession = '软件工
程';
2). ignore index : 忽略指定的索引。
explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工
程';
3)force index : 强制使用索引。
explain select * from tb_user force index(idx_user_pro) where profession = '软件工
程';
2.6.5 覆盖索引
尽量使用覆盖索引,减少select *。 那么什么是覆盖索引呢? 覆盖索引是指 查询使用了索引,并
且需要返回的列,在该索引中已经全部能够找到 。
即 select返回字段必须是索引有的字段,否则要回表
Extra | 含义 |
---|---|
Using where; UsingIndex | 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据 |
Using indexcondition | 查找使用了索引,但是需要回表查询数据 |
2.6.6 前缀索引
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让
索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建
立索引,这样可以大大节约索引空间,从而提高索引效率。
1). 语法
--n为截取字符串的长度
create index idx_xxxx on table_name(column(n)) ;
2). 前缀长度
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,
索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
create index idx_email_5 on tb_user(email(5));
-- 查出截取前几个准确率最高
select count(distinct substring(email,1,5)) / count(*) from tb_user ;
2.7 索引设计原则
1). 针对于数据量较大(百万),且查询比较频繁的表建立索引。
2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索
引。
3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,
避免回表,提高查询效率。
6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增
删改的效率。
create unique index idx_user_phone_name on tb_user(phone,name); 1
7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含
NULL值时,它可以更好地确定哪个索引最有效地用于查询。
三. SQL优化
3.1 插入数据
3.1.1 insert
1)优化1
一次插入多条
Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
2)优化2
手动控制事务然后再多次插入
3)优化3
主键顺序插入,性能要高于乱序插入。
3.1.2 大批量插入数据
如果一次性需要插入大批量数据(比如: 几百万的记录),使用insert语句插入性能较低,此时可以使
用MySQL数据库提供的load指令进行插入。
可以执行如下指令,将数据脚本文件中的数据加载到表结构中:
-- 客户端连接服务端时,加上参数 -–local-infile
mysql –-local-infile -u root -p
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
--创建表结构
create table ......
-- 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table tb_user fields
terminated by ',' lines terminated by '\n' ;
-- 客户端连接服务端时,加上参数 -–local-infile
mysql –-local-infile -u root -p
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
-- load加载数据
load data local infile '/root/load_user_100w_sort.sql' into table tb_user
fields terminated by ',' lines terminated by '\n' ;
3.2 主键优化
在InnoDB引擎中,数据行是记录在逻辑结构 page 页中的,而每一个页的大小是固定的,默认16K。
那也就意味着, 一个页中所存储的行也是有限的,如果插入的数据行row在该页存储不够,将会存储
到下一个页中,页与页之间会通过指针连接。
页分裂
数据存储不了时进行。
A. 主键顺序插入效果
①. 从磁盘中申请页, 主键顺序插入
②. 第一个页没有满,继续往第一页插入
③. 当第一个也写满之后,再写入第二个页,页与页之间会通过指针连接
④. 当第二页写满了,再往第三页写入
B. 主键乱序插入效果
①. 加入1#,2#页都已经写满了。
但是数据按照顺序是第一页中的数据
②. 此时第一页会取中间值把右边的数据放在新的页
③.再把数据插入到指定的页上面
④那么此时,这三个页之间的数据顺序是有问题的。 1#的下一个页,应该是3#, 3#的下一个页是2#。 所以,此时,需要重新设置链表指针。
页合并
当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间
变得允许被其他记录声明使用。
页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前
或后)看看是否可以将两个页合并以优化空间使用。
注: MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定。
索引设计原则
满足业务需求的情况下,尽量降低主键的长度。
插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
业务操作时,避免对主键的修改。
3.3 order by优化
MySQL的排序,有两种方式:
Using filesort:
通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sortbuffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
Using index :
通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。
对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序
操作时,尽量要优化为 Using index。
Backward index scan,这个代表反向扫描索引,因为在MySQL中我们创建的索引,默认索引的叶子节点是从小到大排序的,而此时我们查询排序时,是从大到小,所以,在扫描时,就是反向扫描,就会出现 Backward index scan。 在MySQL8版本中,支持降序索引,我们也可以创建降序索引。
因为创建索引时,如果未指定顺序,默认都是按照升序排序的,而查询时,一个升序,一个降序,此时
就会出现Using filesort。
创建联合索引(age 升序排序,phone 倒序排序)
create index idx_user_age_phone_ad on tb_user(age asc ,phone desc);
注:要满足最左匹配 此时有 条件,此时顺序是必要的。
order by优化原则:
A. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
B. 尽量使用覆盖索引。
C. 多字段排序, 一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
D. 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小
sort_buffer_size(默认256k)。
3.4 group by优化
在分组操作中,需要通过以下两点进行优化,以提升性能:
A. 在分组操作时,可以通过索引来提高效率。
B. 分组操作时,索引的使用也是满足最左前缀法则的
3.5 limit优化
在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。
优化思路: 一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查
询形式进行优化
3.6 count优化
3.6.1 概述
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count() 的时候会直接返回这个
数,效率很高; 但是如果是带条件的count,MyISAM也慢。
InnoDB 引擎就麻烦了,它执行 count() 的时候,需要把数据一行一行地从引擎里面读出
来,然后累积计数。
可以采用redis,但是带条件的SQL又比较麻烦,而且每次insert 和delete的时候都会修改redis
3.6.2 count用法
count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是
NULL,累计值就加 1,否则不加,最后返回累计值。
用法:count(*)、count(主键)、count(字段)、count(数字)
count用法 | 含义 |
---|---|
count(主键) | InnoDB 引擎会遍历整张表,把每一行的 主键id 值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null) |
count(字段) | 没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。 |
count(数字) | InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。 |
count(*) | InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。 |
按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(),所以尽量使用 count()。
3.7 update优化
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁升级为表锁 。
四. 锁
4.1 概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM(内存)、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
MySQL中的锁,按照锁的粒度分,分为以下三类:
全局锁:锁定数据库中的所有表。
表级锁:每次操作锁住整张表。
行级锁:每次操作锁住对应的行数据。
4.2 全局锁
4.2.1 介绍
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整
性。
4.2.2 语法
1). 加全局锁
flush tables with read lock ;
2). 数据备份
mysqldump -uroot –p1234 itcast > itcast.sql 1
3). 释放锁
unlock tables ;
4.2.3 特点
数据库中加全局锁,是一个比较重的操作,存在以下问题:
1)如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
2)如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql ;
4.3 表级锁
4.3.1 介绍
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、
InnoDB、BDB等存储引擎中。
对于表级锁,主要分为以下三类:
表锁
元数据锁(meta data lock,MDL)
意向锁
4.3.2 表锁
对于表锁,分为两类:
表共享读锁(read lock)
表独占写锁(write lock)
语法:
-- 多个表不加,空格分开
加锁:lock tables 表名... read/write。
释放锁:unlock tables / 客户端断开连接 。
lock tables project collar read;
unlock tables;
结论:读写不共存 写写不共存
4.3.3 元数据锁
meta data lock , 元数据锁,简写MDL。
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维
护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与
DDL冲突,保证读写的正确性。
这里的元数据,可以简单理解为就是一张表的表结构。 即某一张表涉及到未提交的事务时,是不能够修改这张表的表结构的。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
4.3.4 意向锁
1). 介绍
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
案例
客户端一开启一个事务,然后执行DML操作时对表加了行锁,客户端二,想对这张表加表锁时,(会检查当前表是否有对应的行锁从第一行检查到最后一行,效率极低)。
有了意向锁,客户端一执行的时候再对此表加上意向锁,而客户端二在想加锁的时候后通过意向锁就知道了,去除了全行扫描。
意向共享锁(IS): 由语句select ... lock in share mode
添加 。 与 表锁共享锁
(read)兼容,与表锁排他锁(write)互斥。
意向排他锁(IX): 由insert、update、delete、select...for update
添加 。与表锁共
享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
4.4 行级锁
4.4.1 介绍
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。 无索引时提升为类似表锁级别。
三类:
行锁(Record Lock)
锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。
间隙锁(Gap Lock)
锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下支持。
临键锁(Next-Key Lock)
行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。
4.4.2 行锁
1). 介绍
InnoDB实现了以下两种类型的行锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
2). 演示
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。
针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时 就会升级为表锁。
查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data
from
performance_schema.data_locks;
客户端一获取的是id为1这行的共享锁,客户端二是可以获取id为3这行的排它锁的,因为不是同一行数据。 而如果客户端二想获取id为1这行的排他锁,会处于阻塞状态,以为共享锁与排他锁之间互斥。
互斥即阻塞到上一个事务结束。共享锁与排它锁之间互斥。
注:无索引加锁时升起为类似表锁。
4.4.3 间隙锁&临键锁
1.索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁。
2.索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock 退化为间隙锁:
因为该索引非唯一所以可以有多个值,所以会一直向后查找,摘到不满足条件,如查找的是18 会对18加临建锁并对满足的第一个如29,则会给29加间隙锁。
3.索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止
如id>=19 则对19加了行锁,对之后的正无穷加临键锁
注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。