MySQL锁篇
1、MySQL锁介绍
MyISAM和MEMORY存储引擎采取的是表级锁
InnoDB既支持表级锁,又支持行级锁,但默认是行级锁。
- 表级锁:开销小,加锁快,不会死锁。锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢,也会死锁。锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 页面锁:开销和加锁时间,锁定粒度,介于表级行级锁中间,也会死锁。并发度一般。
表级锁适合以查询为主,只有少量按索引条件更新数据的应用。
行级锁适合有大量按索引条件并发,更新少量数据,同时又有并发查询的应用。
2、MySQL表级锁
2.1、表级锁介绍
由MySQL SQL layer层实现。
MySQL的表级锁有两种:
一种是表锁。一种是元数据锁(meta data lock,MDL)。
show status like 'table%;
-- able_locks_immediate:产生表级锁定的次数;
-- able_locks_waited:出现表级锁定争用而发生等待的次数
2.2、表锁介绍
2.2.1 表现模式形式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)
2.2.2 手动增加表锁
lock table 表名称 read(write),表名称2 read(write);
2.2.3 查看表锁情况
show open tables;
2.2.4 删除表锁
unlock table;
2.3 表锁演示
2.3.1环境准备
-- 新建表
CREATE TABLE mylock (
id int(11) NOT NULL AUTO_INCREMENT,
NAME varchar(20) DEFAULT NULL,
PRIMARY KEY (id));
INSERT INTO mylock (id,NAME) VALUES (1, 'a');
INSERT INTO mylock (id,NAME) VALUES (2, 'b');
INSERT INTO mylock (id,NAME) VALUES (3, 'c');
INSERT INTO mylock (id,NAME) VALUES (4, 'd');
2.4 读锁演示
2.4.1、表读锁
session1(Navicat)、session2(mysql)
1、session1: lock table mylock read; -- 给mylock表加读锁
2、session1: select * from mylock; -- 可以查询
3、session1:select * from tdep; --不能访问非锁定表
4、session2:select * from mylock; -- 可以查询 没有锁
5、session2:update mylock set name='x' where id=2; -- 修改阻塞,自动加行写锁
6、session1:unlock tables; -- 释放表锁
7、session2:update mylock set name='x' where id=2; -- 这次就修改执行完成
8、session1:select * from tdep; --也可以访问
2.4.2、表写锁
1、session1: lock table mylock write; -- 给mylock表加写锁
2、session1: select * from mylock; -- 实测不可以查询
3、session1:select * from tdep; --不能访问非锁定表
4、session1:update mylock set name='y' where id=2; --可以执行
5、session2:select * from mylock; --别进程写锁, 查询阻塞
6、session1:unlock tables; -- 释放表锁
7、session2:select * from mylock; -- 别进程解锁后,才能查询
8、session1:select * from tdep; --可以访问
3、元数据锁 MDL (metaDataLock)
作用是保护表的元数据信息一致性,解决或者保证DDL操作与DML操作之间的一致性。
当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。
1、session1: begin; --开启事务
select * from mylock;--加MDL读锁
2、session2: alter table mylock add f int; -- 修改表结构阻塞
3、session1:commit; --提交事务 或者 rollback 释放读锁
4、session2:alter table mylock add f int; --修改完成
4、行级锁
4.1、行级锁介绍
InnoDB存储引擎实现,行锁必须有索引才能实现,不然会自动锁全表,那么就不是行锁了。
4.1.1 按锁定范围来说,分为三种:
- 记录锁(Record Locks):锁定索引中一条记录。 主键指定 where id=3
- 间隙锁(Gap Locks): 锁定记录前、记录中、记录后的行 RR隔离级 (可重复读)-- MySQL默认隔离级
- Next-Key锁: 记录锁 + 间隙锁
4.1.2 按功能来说,分为两种:
共享读锁(S):当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作。
但不允许进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
SELECT * FROM 表 WHERE 条件 LOCK IN SHARE MODE ;-- 共享读锁 手动添加
排他写锁(X):一个事务对某几行上写锁时,其他事务允许读,但不能写,更不允许上锁。
4.1.2.1、自动加 DML
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁
4.1.2.2、手动加
SELECT * FROM 表名 WHERE 条件 FOR UPDATE;
示例:
1.
BEGIN
select * from testdemo where id =1 for update
在另外一个session中
update testdemo set c1 = '1' where id = 2 成功
update testdemo set c1 = '1' where id = 1 等待
2.BEGIN
update testdemo set c1 = '1' where id = 1
在另外一个session中
update testdemo set c1 = '1' where id = 1 等待
3.
BEGIN
update testdemo set c1 = '1' where c1 = '1' 在另外一个session中
update testdemo set c1 = '2' where c1 = '2' 等待
4.2 行锁演示
InnoDB行锁是通过给索引上的索引项加锁来实现的
只有通过索引条件检索的数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
4.2.1 行读锁
-- 查看行锁状态
show STATUS like 'innodb_row_lock%';
- Innodb_row_lock_current_waits:当前正在等待锁定的数量;
- Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
- Innodb_row_lock_time_avg:每次等待所花平均时间;
- Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
- Innodbrowlockwaits:系统启动后到现在总共等待的次;
注:使用索引加行锁 ,未锁定的行可以访问
总结:索引的行读锁,所有session的这个索引条件都锁死,修改不了。
1、session1: begin;-- 开启事务未提交
select * from mylock where id=1 lock in share mode; -- 手动加id=1的行读锁,使用索引
2、session2:update mylock set name='y' where id=2; -- 未锁定该行可以修改
3、session2:update mylock set name='y' where id=1; -- 锁定该行修改阻塞 锁定超时
4、session1:update mylock set name='y' where id=1; -- 锁定该行修改阻塞 锁定超时
5、session1: commit; --提交事务 或者 rollback 释放读锁
6、session2:update mylock set name='y' where id=1; --修改成功
4.2.2 行读锁升级为表锁
注:未使用索引行锁升级为表锁
总结:非索引的行读锁,行锁失效,锁表,自己session所有条件啥影响都没,别session所有条件修改锁的死死。
1、session1: begin;-- 开启事务未提交
select * from mylock where name='c' lock in share mode;-- 手动加name='c'的行读锁,未使用索引
2、session2:update mylock set name='y' where id=2; -- 修改阻塞 未用索引行锁升级为表锁
3、session1: commit; --提交事务 或者 rollback 释放读锁
4、session2:update mylock set name='y' where id=2; --修改成功
4.2.3 行写锁
主键索引产生记录锁
总结:索引键的行写锁,条件不失效,所有session查询都没影响,但别session加锁影响,当前session没
1、session1: begin;--开启事务未提交
select * from mylock where id=1 for update;--手动加id=1的行写锁
2、session2:select * from mylock where id=2 ; -- 可以读 不加锁
3、session2: select * from mylock where id=1 ; -- 可以读 不加锁
4、session2: select * from mylock where id=1 lock in share mode; --加读锁被阻塞
5、session1: select * from mylock where id=1 lock in share mode; --不阻塞
6、session1:commit; -- 提交事务 或者 rollback 释放写锁
7、session2:执行成功
4.2.3 间隙锁
4.2.3.1 什么是间隙锁?
间隙锁是一个在索引记录之间的间隙上的锁。当我们采用范围查询时,InnoDB会对这个范围内的数据进行加锁。
示例1:
比如有id为:1、3、5、7 的4条数据,我们查找1-7范围的数据。
那么1-7都会被加上锁。2、4、6也在 1-7的范围中,但是不存在这些数据记录,这些 2、4、6就被称为间隙。
示例2:
我们向表中新增一条数据age=20,这条数据在本来是没有的,在insert还没有提交的时候查询age>15 and a.age<25,这个时候就会触发间隙锁,我们必须等待insert提交后才能执行select语句。
4.2.3.2 为什么会出现间隙锁?
在MySQL的innoDB引擎中,如果操作的是一个区间的数据,会锁住这个区间所有的记录。
即使这个记录不存在,这个时候另一个会话去插入这个区间的数据,就必须等待上一个结束。
注:对于主键索引或唯一索引,如果where条件不能全命中,也会加间隙锁,全命中则加记录锁。
间隙锁的作用:
保证某个间隙内的数据在锁定情况下不会发生任何变化。
当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定。
4.2.4死锁
两个 session 互相等等待对方的资源释放之后,才能释放自己的资源,造成了死锁.
1、session1: begin;-- 开启事务未提交
update mylock set name='m' where id=1;-- 手动加行写锁 id=1 ,使用索引
2、session2:begin;-- 开启事务未提交
update mylock set name='m' where id=2;-- 手动加行写锁 id=2 ,使用索引
3、session1: update mylock set name='nn' where id=2; -- 加写锁被阻塞
4、session2:update mylock set name='nn' where id=1; -- 加写锁会死锁,不允许操作
4.3 面试题
系统运行一段时间后,数据量已经很大,这时A表需要增加个字段,并发量白天晚上都很大,请问怎么修改表结构?
考点:修改表结构会导致表锁,数据量大,修改数据时间久,可能会导致大量用户阻塞,无法访问。
- 1.首选创建一个和要修改表一样结构的空表。
- 2.执行我们需要对表结构进行修改的操作,然后复制原表数据到新表里面。
- 3.在原表创建一个触发器,在数据复制的过程中,将原表的更新数据的操作,全部更新到新的表中。
- 4.复制完成后,用rename table新表代替原表,会默认删除原表。