文章目录
- MySQL锁
- 为什么要锁?
- MySQL锁介绍
- 锁的划分类别
- 表级锁和行级锁之间的区别
- MySQL表级锁介绍
- 1.MySQL的表级锁有两种:
- 2.查看表级锁定的争用状态变量
- 3.表锁介绍
- 4.实例
- 4.1创建表并添加数据
- 4.2表读锁
- 4.3表写锁
- 4.4元数据锁
- 疑问
- MySQL行级锁介绍
- 1.InnoDB行级锁,
- 2.查看InnoDB所使用的行级锁定争用状态查看:
- 3.两阶段锁
- 4.行读锁
- 5.行读锁升级为表锁
- 6.行写锁
- 7.按锁定范围区分
- 7.1创建表
- 7.2记录锁(Record Lock)
- 7.3间隙锁(Gap Lock)
- 7.4临键锁(Next-Key Locks)
MySQL锁
为什么要锁?
事务并发
- 幻读:主要是在另一个事务在查询时插入数据
- 脏读:主要是在另一个事务在查询时更新数据
- 不可重复读:主要是在另一个事务在查询时更新数据并提交
事务并发的三大问题都是数据库读一致性问题:必须有数据库提供一定的事务隔离机制来解决。
事务隔离
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
未提交读(Read Uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read Committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable Read) | 不可能 | 不可能 | 对InnoDB不可能 |
串行化(Serializable) | 不可能 | 不可能 | 不可能 |
事务隔离方案
- 当前读:在读取数据前,对其加锁,组织其他事务对数据进行修改——Lock Based Concurrency Control(LBCC)
- 快照读:生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取——Multi Version Concurrency Control(MVCC)
MySQL锁介绍
锁的划分类别
按照锁的粒度来分,有三种级别的锁定机制:
- 全局锁:锁的是整个database。由MySQL的SQL layer层实现的。
- 表级锁:锁的是某个table。由MySQL的SQL layer层实现的。
- 行级锁:锁的是某行数据,也可能锁定行之间的间隙。由某些存储引擎实现,比如InnoDB
按照锁的功能
- 共享读锁
- 排他写锁
按照锁的实现方式
- 悲观锁
- 乐观锁
表级锁和行级锁之间的区别
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度最高。
MySQL表级锁介绍
1.MySQL的表级锁有两种:
- 表锁
- 元数据锁(meta data lock,MDL)
2.查看表级锁定的争用状态变量
SHOW STATUS LIKE 'table%';
结果集:
Table_locks_immediate:产生表级锁定的次数
Table_locks_waited:出现表级锁定争用而发生等待次数
3.表锁介绍
- 表锁有两种:
- 表共享读锁(Table Read Lock)
- 表独占写锁(Table Write Lock)
- 手动增加表锁
lock table 表名称 read(write),表名称2 reda(write),其他;
- 查看表锁情况
SHOW OPEN TABLES;
- 删除表锁
UNLOCK TABLES;
4.实例
4.1创建表并添加数据
-- 创建mylock表
CREATE TABLE mylock(
id INT(11) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (id)
);
-- 为mylock表添加数据
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');
4.2表读锁
-- 会话1:给mylock表加读锁
LOCK TABLE mylock READ;
-- 会话1:可以查询已经加锁的表
SELECT * FROM mylock;
-- 会话1:不能查询非锁定表
SELECT * FROM tdep;
-- 会话2:可以查询,没有锁
SELECT * FROM mylock;
-- 会话2:不能查询非读锁的表
SELECT * FROM tdep;
-- 会话2:修改阻塞,自动加行写锁
UPDATE mylock SET NAME = 'x' WHERE id = 2;
-- 会话1:释放表锁
UNLOCK TABLES;
-- 会话2:修改执行完成
UPDATE mylock SET NAME = 'x' WHERE id = 2;
-- 返回结果
1 queries executed, 1 success, 0 ERRORS, 0 WARNINGS
4.3表写锁
-- 会话1:给mylock表添加写锁
LOCK TABLE mylock WRITE;
-- 会话1:可以查询该表
SELECT * FROM mylock;
-- 会话1:不能访问非锁定表
SELECT * FROM tdep;
-- 会话1:修改语句可以执行
UPDATE mylock SET NAME = 'y' WHERE id = 2;
-- 会话2:不能查询未加锁的表
SELECT * FROM tdep;
-- 会话2:查询阻塞
SELECT * FROM mylock;
-- 会话1:释放表锁
UNLOCK TABLES;
-- 会话2:正常执行查询
SELECT * FROM mylock;
4.4元数据锁
- MDL在访问一个表的时候会自动加上。MDL的作用是确保读写的正确性。
- 读锁之间不互斥,读写锁之间,写锁之间互斥。
- MySQL5.5,当对一个表做增删改查操作的时候,加MDL读锁;当要对表结构变更操作时,加MDL写锁。
-- session1:开启事务
BEGIN;
-- session1:加MDL读锁
SELECT * FROM mylock;
-- session2:可以读表
SELECT * FROM mylock;
-- session2:修改阻塞(后面的会话不论查询还是修改都会阻塞)
ALTER TABLE mylock ADD f INT;
-- session1:修改成功后session2也执行完毕
ALTER TABLE mylock DROP c;
-- session1:提交事务或者rollback回滚事务从而释放读锁
ROLLBACK;
COMMIT;
疑问
当session1在执行ALTER TABLE mylock DROP c;语句后session2自动解锁,执行语句ALTER TABLE mylock ADD d INT;修改表。
MySQL行级锁介绍
- InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索的数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
1.InnoDB行级锁,
- 按照锁定范围来分,有三种:
- 记录锁(Record Locks):锁定索引中的一条记录。
- 间隙锁(Cap Locks):要么锁住索引记录中间的值,要么锁住第一个索引记录前面的值或者最后一个索引记录后面的值。
- 临键锁(Next-Key Locks):是索引记录上的记录锁和在索引记录之前的间隙锁的组合。
- 按照锁定功能来分,有两种:
- 共享锁(s)Share Locks:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- 排他锁(x)Exclusive Locks:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
- 对于update,delete,insert语句,InnoDB会自动动给涉及数据集加排他锁(x)
- 而对于普通的select语句,InnoDB不会加任何锁
-- 手动添加共享锁
select * from table_name where …… lock in share mode
-- 手动添加排他锁
select * from table_name where …… for update
2.查看InnoDB所使用的行级锁定争用状态查看:
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:从系统启动到现在等待最常的一次所花的时间
Innodb_row_lock_waits:系统启动后到现在总共等待的次数
3.两阶段锁
- 加锁阶段:只加锁,不放锁
- 解锁阶段:只放锁,不加锁
4.行读锁
- 使用索引加行锁,未加锁的行可以访问
-- 查看行锁状态
SHOW STATUS LIKE 'innodb_row_lock%';
-- 会话1:开启事务
BEGIN;
-- 会话1:手动加id=1的行读锁,使用索引
SELECT * FROM mylock WHERE id = 1 LOCK IN SHARE MODE;
-- 会话2:开启事务
BEGIN;
-- 会话2:对未锁定行可以修改
UPDATE mylock SET NAME = 'z' WHERE id = 2;
-- 会话2:对加锁行可以查询
SELECT * FROM mylock WHERE id = 1;
-- 会话2:对锁定行修改阻塞
UPDATE mylock SET NAME = 'y' WHERE id = 1;
-- 会话1:回滚事务或提交事务
ROLLBACK;
COMMIT;
-- 会话2:回滚事务或提交事务
ROLLBACK;
COMMIT;
5.行读锁升级为表锁
- 未使用索引行锁升级为表锁
-- session1:开启事务
BEGIN;
-- session1:手动加name='c'的行读锁,未使用索引
SELECT * FROM mylock WHERE NAME = 'c' LOCK IN SHARE MODE;
-- session2:开启事务
BEGIN;
-- session2:可以查询
SELECT * FROM mylock WHERE NAME = 'c';
-- session2:修改阻塞,未使用索引行锁升级为表锁
UPDATE mylock SET NAME = 'b' WHERE id = 2;
-- session1:提交事务或回滚事务
ROLLBACK;
COMMIT;
-- session2:提交事务或回滚事务
ROLLBACK;
COMMIT;
6.行写锁
- 主键索引产生记录锁
-- session1:开启事务
BEGIN;
-- session1:手动加id=1的行写锁
SELECT * FROM mylock WHERE id = 1 FOR UPDATE;
-- session2:开启事务
BEGIN;
-- session2:可以访问
SELECT * FROM mylock WHERE id = 2;
-- session2:可以读,不加锁
SELECT * FROM mylock WHERE id = 1;
-- session2:可以读,加锁阻塞
SELECT * FROM mylock WHERE id = 1 LOCK IN SHARE MODE;
-- session1:提交或回滚事务
ROLLBACK;
COMMIT;
-- session2:事务提交或回滚
ROLLBACK;
COMMIT;
7.按锁定范围区分
7.1创建表
-- 创建表news
CREATE TABLE news(
id INT(11) PRIMARY KEY ,
number INT(11)
);
-- 添加数据
INSERT INTO news(id,number) VALUES(1,2);
INSERT INTO news(id,number) VALUES(3,4);
INSERT INTO news(id,number) VALUES(6,5);
INSERT INTO news(id,number) VALUES(8,5);
INSERT INTO news(id,number) VALUES(10,5);
INSERT INTO news(id,number) VALUES(13,11);
-- 添加非唯一索引
ALTER TABLE news ADD INDEX inx_num(number);
7.2记录锁(Record Lock)
-- 记录锁:只对指定索引的行加锁
-- session1:开启
START TRANSACTION;
-- session1:唯一性索引(唯一/主键)等值查询,精准匹配
SELECT * FROM news WHERE id = 3 FOR UPDATE;
-- session2:非锁定行可以修改
UPDATE news SET number = 8 WHERE id = 8;
-- session2:修改id阻塞
UPDATE news SET number = 5 WHERE id = 3;
-- session1:提交事务或回滚事务
ROLLBACK;
COMMIT;
7.3间隙锁(Gap Lock)
- 间隙表id和number都在间隙内则阻塞
-- session1:
START TRANSACTION;
-- session1:手动添加行写锁
SELECT * FROM news WHERE number = 4 FOR UPDATE;
-- session2:
START TRANSACTION;
-- session2:执行阻塞
INSERT INTO news VALUE(2,4);
INSERT INTO news VALUE(2,2);
INSERT INTO news VALUE(4,4);
INSERT INTO news VALUE(4,5);
-- session2:执行成功
INSERT INTO news VALUE(7,5);
INSERT INTO news VALUE(9,5);
INSERT INTO news VALUE(11,5);
-- session1:提交事务或回滚事务
ROLLBACK;
COMMIT;
-- session2:提交事务或回滚事务
ROLLBACK;
COMMIT;
- 主键索引产生间隙锁
-- session1:开启事务
START TRANSACTION;
-- session1:手动添加主键索引间隙
SELECT * FROM news WHERE id > 2 AND id < 8 FOR UPDATE;
-- session2:
START TRANSACTION;
-- session2:执行成功
INSERT INTO news VALUE(9,8);
UPDATE news SET number = 10 WHERE id = 10;
-- session2:执行阻塞
UPDATE news SET number = 8 WHERE id = 8;
INSERT INTO news VALUE(2,1);
INSERT INTO news VALUE(7,3);
-- session1:提交事务或回滚事务
ROLLBACK;
COMMIT;
-- session2:提交事务或回滚事务
ROLLBACK;
COMMIT;
- 非主键索引产生间隙锁,影响范围
-- session1:开启事务
START TRANSACTION;
-- session1:由于number最大为11,所以锁住了(11,+oo)
SELECT * FROM news WHERE number = 13 FOR UPDATE;
-- session2:
START TRANSACTION;
-- session2:执行成功:number不在(11,+oo)内
INSERT INTO news VALUE(11,11);
INSERT INTO news VALUE(14,3);
INSERT INTO news VALUE(15,10);
UPDATE news SET number = 10 WHERE id = 10;
UPDATE news SET number = 1 WHERE id = 13;
-- session2:执行阻塞
UPDATE news SET number = 12 WHERE id = 13;-- id为13是number11的范围不允许执行
INSERT INTO news VALUE(12,12);-- number12大于11了,不允许执行
-- session1:事务提交或回滚
ROLLBACK;
COMMIT;
-- session2:事务提交或回滚
ROLLBACK;
COMMIT;
7.4临键锁(Next-Key Locks)
-- session1:开启事务
START TRANSACTION;
-- session1:锁住(3,6]
SELECT * FROM news WHERE id > 3 AND id < 6 FOR UPDATE;
-- session1:(3,6]添加
INSERT INTO news(id,number) VALUES(4,2);
-- session1:(3,6]以外成功修改
UPDATE news SET number = 8 WHERE id = 8;
-- session1:查询已经更改
SELECT * FROM news;
-- session2:开启事务
BEGIN;
-- session2:(3,6]以外修改成功
UPDATE news SET number = 5 WHERE id = 3;
-- session2:(3,6]以外添加成功
INSERT INTO news(id,number) VALUES(7,2);
-- session1:查询已经更改
SELECT * FROM news;
-- session2:(3,6]以外修改成功
UPDATE news SET number = 9 WHERE id = 8;
-- session2:(3,6]修改阻塞
UPDATE news SET number = 7 WHERE id = 6;
-- session2:(3,6]添加阻塞
INSERT INTO news(id,number) VALUES(4,2);
-- session1:提交事务或回滚事务
ROLLBACK;
COMMIT;
-- session2:事务提交或回滚
ROLLBACK;
COMMIT;