1、锁
1.1、锁的概述
锁就不需要多介绍了,多个用户访问共享数据资源,如何保证数据并发访问的一致性、有效性是数据库最重要的问题。同时,锁冲突也是影响一个数据库并发性能最重要的因素。
MySQL 中锁的划分有三类:
- 全局锁:锁定数据库中所有表
- 表级锁:每次操作锁定整张表
- 行级锁:每次操作锁住对应的行
1.2、全局锁
全局锁会锁定整个数据库实例,使得整个数据库处于只读状态,后续的 DML、DDL 语句都会被阻塞住。
全局锁的使用场景主要就是做全库的逻辑备份,对所有表进行锁定,以保证数据的一致性和完整性。
1.2.1、语法
全局锁语法:
flush tables with read lock;
备份数据:
注意:这是 mysql 的一个工具,不是在 sql 窗口执行;
mysqldump -uroot -p database_name > file.sql;
解锁:
unlock tables;
1.2.2、测试
给 saprk 数据库加全局锁:
测试从其他客户端插入数据(发现被阻塞):
备份数据库:
备份成功后,被阻塞的插入语句执行成功:
1.2.3、特点
全局锁是一个比较重的操作,存在以下问题:
- 如果在主库上备份,那么备份期间都不能执行更新,业务基本停滞
- 如果在从库上备份,那么备份期间从库不能从主库同步 binlog,会导致主从延迟
在 InnoDB 引擎中,我们在备份时加上 --single-transaction 参数来完成不加锁的一致性数据备份;
mysqldump --single-transcation -uroot -p database_name > file.sql;
1.3、表级锁
表级锁每次锁住整张表。发生锁冲突的概率最大,并发度最低。主要分为三类:
- 表锁
- 元数据锁
- 意向锁
1.3.1、表锁
表锁主要分为:
- 表共享锁(读锁)
- 表独占锁(写锁)
这有点像我们 zookeeper 中分布式锁的概念,在 zookeeper 的分布式锁中也分为共享锁和排它锁,其中排它锁指的就是独占锁(写),共享锁就是读锁。
语法
- 加锁:lock tables 表名 read/lock
- 释放锁:unlock tables 或者 关闭客户端
读锁测试
给表 student 加上表锁:
可以看到,当 student 表被加上表锁时,其它客户端只能对该表只读不写;
写锁测试
可以看到,当 student 被加上写锁时,其它客户端既不能读更不能写;为的是保证数据库的一致性;
1.3.2、元数据锁(meta data lock,MDL)
MDL 加锁过程是自动的,不需要显示声明,在访问一张表的时候会自动加上。MDL 锁主要是维护表数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作(也就是 不能修改表结构)。
在 MySQL5.5 中引入 MDL,当对一张表进行增删改查的时候,加 MDL 读锁(共享);当对表结构进行变更操作时,加 MDL 写锁(排它);
简单来说就是,在操作表数据的时候不能修改表结构;
1.3.3、意向锁
意向锁是为了解决行锁和表锁的冲突问题。在 DML 执行时,会给要操作的数据行加一个行锁,如果此时有线程需要对这张表加一个表锁就会发生冲突。所以 InnoDB 就引入了意向锁,使得表锁不用一行一行去检查当前行是否已经加锁;
语法:
- 意向共享锁(IS):由 select ... lock in share mode 添加(和表锁的读锁兼容,写锁互斥)
- 意向排他锁(IX):由 insert、update、delete、select ... for update 添加(和表锁的读锁和写锁都互斥)
注意:意向锁之间是不会互斥的;
加锁过程:
- begin
- update ... (加行锁的同时给全表加一个意向锁)
- commit(释放锁)
这样,当需要对全表加表锁的时候,就不需要逐行去检查是否有行锁了;
1.4、行级锁
行级锁每次操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低,并发度最高。行级锁是 InnoDB 和 MyISAM 一大区别(MyISAM 支持的最细粒度是表级锁)。
InnoDB 的数据是基于索引组织的,所以行锁是通过对索引上的索引项加锁来实现的,而不是对记录加锁。对于行级锁,主要分为三类:
- 行锁(record lock):锁定单行记录,防止其它事务进行 update 和 delete;
- 间隙锁(gap lock):锁定索引记录间隙(不含记录),确保索引记录间隙不变,防止其它事务在这个间隙进行 insert,产生幻读;
- 临键锁(next-key lock):行锁和间隙锁的组合,同时锁住数据和数据前面的间隙;
1.4.1、行锁
InnoDB 实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其它事务获得相同数据的排它锁。(也就是说,共享锁和共享锁之间是兼容的,共享锁和排它锁之间是互斥的)
- 排它锁(X):允许获取排它锁的事务更新数据,阻止其它事务获得相同的数据的共享锁和排它锁(也就是说,排它锁和共享锁和排它锁之间都是互斥的)
默认情况下,InnoDB 在可重复读(repeatable read)事务隔离级别运行,InnoDB 使用 next-key 锁进行搜索和索引,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁
- InnoDB 的行锁是针对于索引加的锁,不通过索引条件检索数据,那么 InnoDB 将对表中所有记录加锁,此时就会升级为表锁;
上一节讲SQL优化时,关于 update 语句的优化就是:
当我们在使用 update 语句的时候,一定要注意尽量使用索引字段做为更新条件去更新,否则就会出现行锁升级为表锁,并发性能就会降低;因为 InnoDB 的行锁是针对索引加的锁,而不是针对记录加的锁!
1.4.2、间隙锁/临键锁
注意:这一块知识点不需要记,了解即可。
默认情况下,InnoDB 在可重复读(repeatable read)事务隔离级别运行,InnoDB 使用 next-key 锁进行搜索和索引,以防止幻读。
- 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
- 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询要求时,next-key lock 退化为间隙锁
- 索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止
注意:间隙锁唯一目的是防止其它事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。
-- 假设 age 列存在普通索引 idx_stu_age
select * from student where age = 12 lock in share mode;
上面这条查询语句会加一个共享锁,同时,因为是 age 是普通索引,所以除了给这一行记录加一个共享锁之外,还会在 age = 3 和下一位 age != 3 的记录中间加一个间隙锁;给 age=3 之前的记录都加一个临键锁。
select * from student where >= 19 lock in share mode;
上面这条范围查询语句除了会给 age = 19 行这条记录锁住之外(共享锁),还会给 age > 19 的第一个记录加一个临键锁,以及给正无穷到 age >19 的第一个记录之间的记录加一个临键锁。
总之,主要记住一点:间隙锁锁的是数据之间的间隙,不锁记录;而临键锁既会锁住该记录,也会锁住该记录之前的所有间隙;
总结
-
概述
-
锁是并发访问时,解决数据访问一致性、有效性问题的一种机制
-
-
全局锁
-
加锁后,整个数据库只读不写,影响业务
-
性能差,主要用于数据逻辑备份
-
-
表级锁
-
表锁:读锁,写锁,把整张表锁住
-
元数据锁:防止 DML 语句和 DDL 语句冲突
-
意向锁:规避行锁和表锁在加锁时的冲突问题,避免表锁在加锁时逐行去检查每行的加锁情况
-
-
行级锁
-
行锁:只有共享锁之间可以兼容,共享锁和排它锁之间,排它锁和排它锁之间都冲突
-
间隙锁:解决多个事务并发操作时出现幻读的情况
-
临键锁:行锁和间隙锁的组合
-