场景
模拟并发场景下,其中一个会话在执行更新表操作,同时另一个会话执行查询操作。该查询操作本身很简单,查询的数据量也很小,但是查询速度很慢。
场景复原
模拟更新操作导致锁表
假设我们有一张商品信息表t_good_info
,现在更新ID为1的商品价格price
。
这里使用sleep(N)
函数来模拟耗时操作。
在session1中执行:
update t_good_info set price = sleep(20) where id = 1;

执行查询
假设另一个用户此时查询商品信息,如查询id为2的商品信息。
select * from t_good_info where id = 2;


从途中看到,仅仅一条查询就耗时28秒!
问题排查
- 首先先查看一下MySQL的进程状态
show processlist;

从图中看到查询操作被阻塞,状态显示其正在等待表级锁。
- 查看表引擎
由于在写操作时出现了表锁,因此查看该表的存储引擎是否是MyISAM。
show create table t_good_info;

从
ENGINE
字段可以看出该表使用的是MyISAM。
MyISAM 与表锁
MyISAM存储引擎采用的锁是表级锁,表级锁有以下特点:
开销小,粒度大,不易出现死锁,锁竞争概率大,并发效果差。
- 查看表级锁争用情况
show status like 'table%';

table_locks_waited数值越大,则表级锁争用越严重。
- 表级锁的两种模式
表级锁有两种体现:读锁、写锁。其表现形式也类似Java中的读写锁。
如果表加上读锁,则其他对该表的读操作可以并行执行,但写操作会被阻塞;如果是写锁,则对该表的读、写操作都会串行执行。
MyISAM 表级锁(读写锁)效果演示
- 读锁
先手动给表t_good_info
加上读锁。
lock table t_good_info read;

开启两个会话查询
t_good_info
表,发现可以并行执行。


此时尝试做写操作,发现写操作被阻塞。


释放读锁
unlock tables;

此时被阻塞的写操作得到执行。可以看到该插入操作执行时间为54秒,期间正是在等待读锁释放。

- 写锁
写锁会在表执行写操作时自动加锁,引起查询阻塞,表现形式即本文场景中提到的情况。
解决方案
- 不使用MyISAM引擎
MySQL 8.0中已经去除MyISAM引擎的表,也表明MySQL已不建议使用该引擎。 - 在读操作远频繁于写操作时使用
由于表锁开销小,在读操作使用频率远远大于写操作的场景下依然可以使用MyISAM引擎。
网友评论