本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
锁是什么?
锁,是计算机协调多个线程/多进程 访问某资源的一种机制。
何为资源?
- 计算资源:CPU , RAM( 一般指随机存取存储器。随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器) ,I/O 等。
- 数据:共享的资源,数据库要保证共享资源的一致性和有效性。
有什么弊端?
锁冲突,影响了数据库并发的性能。
锁分类
按性能分类:
| 悲观锁 | 乐观锁 | |
|---|---|---|
| 实现方式 | 用版本比对来实现 | |
| 举例 | 读锁、写锁 |
按数据库操作分类:
| 读锁 | 写锁 | |
|---|---|---|
| 别名 | 共享锁/S锁(Shared) | 独享锁/X锁(eXclusive) |
| 解释 | 同一份数据,多个读操作互不影响。 | 当前写操作没有完成,阻断其他读写 |
| 效果 | 当前session和其他session都可以读;当前session写操作报错;其他session写操作等待; | 当前session读写可以;其他session读写等待/阻塞; |
按粒度分类:
| 行锁 | 表锁 | |
|---|---|---|
| 解释 | 锁一行 | 锁整张表 |
| mysql中 | Innodb表默认 | MyISAM表默认 |
| 加锁性能 | 加锁慢,开销大 | 加锁快,开销小 |
| 并发度 | 高 | 低 |
| 锁冲突 | 粒度小,冲突少 | 粒度大,冲突多 |
| 死锁 | 不会 | 会 |
| 使用场景 | 整表前移 |
附:不同引擎的锁和事务
| InnoDB | MyISAM | |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 行锁 | 默认:select时候不加锁(串行例外),update、delete、insert会加行锁。 | 不支持 |
| 表锁 | 操作不当会锁表(后文介绍):无索引行锁会升级为表锁 | 默认:执行select给表加读锁,update \delete\insert 给表加写锁。 |
InnoDB中锁(行锁)和事务关系密切
回顾MySQL事务隔离级别
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| read uncommitted 读未提交 | 有 | 有 | 有 |
| read committed 读已提交 | 无 | 有 | 有 |
| repeatable read 可重复读 | 无 | 无 | 有 |
| serializable 串行化 | 无 | 无 | 无 |
MySQL默认可重复读事务下的锁详解
MySQL 为保证可重复读在锁上使用了间隙锁和临键锁
间隙锁(Gap Lock):为避免幻读,范围写操作时会锁定范围,举个例子:
- sessionA执行:update product set product_amount = 10 where id>1 and id<100;
- sessionB执行:update product set product_name = '鸿星尔克男鞋' where id = 56;
此时sessionB是无法执行成功的,因为sessionA的操作,在可重复读模式下,实现了间隙锁:即(1,100] 不包含1到100,且包含100,范围内都被锁定。注:仅会在可重复读的事务下触发间隙锁。
临键锁:
就是:行锁+间隙锁 = 临键锁
使用锁的命令
注:在不使用数据库连接工具时,仅用命令行查询时,命令末尾使用\G 使得查询结果的展示结构旋转90度变成纵向
【表锁】相关命令
查看是否有表锁
show OPEN TABLES where In_use > 0;
删除表锁
unlock tables
使用MyISAM引擎
CREATE TABLE `表的名字` (
`id` INT (32) NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR (20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = MyISAM DEFAULT CHARSET = utf8;
手动添加表锁(共享锁/S锁/读锁)
lock table 表的名字 read
手动添加表锁(独占锁/X锁/写锁)
lock table 表名 write
【锁】相关命令
查看所有表上加过的锁
show open tables
删除表锁
unlock tables
【行锁】相关命令
查看全部行锁信息
show status like 'innodb_row_lock%';
查看全部行锁信息的解释
| 名称 | 解释 (单位ms) |
|---|---|
| innodb_row_lock_current_waits | 当前正在等待锁的会话有多少个 |
| innodb_row_lock_time | 从系统启动到现在锁定总时间 |
| innodb_row_lock_time_avg | 平均每次等待所需时间 |
| innodb_row_lock_time_max | 从系统启动到现在等待时长最长的时间 |
| innodb_row_lock_waits | 系统启动到现在共计等待次数 |
查看INFORMATION_SCHEMA系统库得知信息
‐‐ 查看事务
select * from INFORMATION_SCHEMA.INNODB_TRX;
查看当前出现的锁
‐‐ 查看当前出现的锁
select * from information_schema.innodb_locks\G;
查看锁等待的对应关系
‐‐ 查看锁等待的对应关系
select * from information_schema.innodb_lock_waits\G;
释放锁
‐‐ 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到
kill trx_mysql_thread_id
查看锁等待,死锁日志
‐‐ 查看锁等待详细信息 查看死锁日志
show engine innodb status\G;
查看进程
show processlist\G
杀死对应进程ID
kill 140
锁的性能优化
死锁
场景举例
SessionA执行:select * from product where id=1 for update;
SessionB执行:select * from product where id=2 for update;
SessionA执行:select * from product where id=2 for update;
SessionB执行:select * from product where id=1 for update;
解决方案
- mysql一般情况会自动察觉死锁,并回滚事务。特殊情况需手动处理。
- 查看锁日志,杀进程。
性能优化
使用索引检索:避免行锁升级为表锁
合理设计索引:缩小锁范围,避免间隙锁
涉及事务加锁的SQL放最后执行
尽可能低事务级别