19 | 为什么我只查一行的语句,也执行这么慢?

MySQL 本身压力大, CPU 占用率高或IO 利用率高。字段 id 和 c,10 万行记录:

19 | 为什么我只查一行的语句,也执行这么慢?_第1张图片

第一类:查询长时间不返回

mysql> select  * from t where id=1;

图 1 查询长时间不返回

表 t 被锁住。show  processlist 。针对状态,分析原因、如何复现,以及如何处理。

1.1等 MDL

19 | 为什么我只查一行的语句,也执行这么慢?_第2张图片
图 2 Waiting for table metadata lock 状态示意图

Waiting for table metadata lock:一个线程在表 t 上请求持有 MDL 写锁,把 select 语句堵住了。

在第 6 篇文章《全局锁和表锁 :给表加个字段怎么有这么多阻碍?》中, MySQL 5.7 版本修改 MDL 加锁策略,不能复现这个场景了。5.7 版本下复现也很容易:

19 | 为什么我只查一行的语句,也执行这么慢?_第3张图片
图 3 MySQL 5.7 中 Waiting for table metadata lock 的复现步骤

 A  lock table 有表MDL 写锁, B 查询需MDL 读锁,等待。

解决办法:找谁有 MDL 写锁, kill 掉

 A是“Sleep”,导致查找起来很不方便。有performance_schema 和 sys 系统库,方便多了。(MySQL 启动时设置performance_schema=on,比 off 有 10% 性能损失)

查询sys.schema_table_lock_waits ,造成阻塞 process id,kill 命令断开连接

19 | 为什么我只查一行的语句,也执行这么慢?_第4张图片
图 4 查获加表锁的线程 id

1.2等flush

表 t 上mysql> select  * from information_schema.processlist where id=1;

线程状态Waiting for table flush,设想原因

图 5 Waiting for table flush 状态示意图

表示:线程对表 t 做 flush。 flush 操作用法:

指定表 t 只关闭表 t:flush tables t  with read lock;

关闭所有打开表:flush tables with  read lock;

都快,除非被堵住。

flush tables 命令被别的语句堵住了,它又堵住 select 。复现

19 | 为什么我只查一行的语句,也执行这么慢?_第5张图片
图 6 Waiting for table flush 的复现步骤

A每行都调用sleep(1),默认执行 10 万秒, t 一直被 A打开 B flush tables t 关闭 t,需等 A结束。C被 flush 命令堵住

19 | 为什么我只查一行的语句,也执行这么慢?_第6张图片
图 7 Waiting for table flush 的 show processlist 结果  

1.3、等行锁

mysql> select  * from t where id=1 lock in share mode; 第 8 篇《事务到底是隔离的还是不隔离的?》

访问 id=1 加读锁,如已有事务在这行上写锁,select 被堵。

19 | 为什么我只查一行的语句,也执行这么慢?_第7张图片
图 8 行锁复现
19 | 为什么我只查一行的语句,也执行这么慢?_第8张图片
图 9 行锁 show processlist 现场

session 占写锁,不提交,B 被堵住的原因。

5.7 版本sys.innodb_lock_waits 表查谁占写锁

mysql> select  * from t sys.innodb_lock_waits where locked_table=`'test'.'t'`\G

19 | 为什么我只查一行的语句,也执行这么慢?_第9张图片
图 10 通过 sys.innodb_lock_waits 查行锁

4 线程罪魁祸首:

KILL QUERY 4没用,表示停止当前正在执行语句。update 语句占行锁,之前执行完,现在 KILL QUERY,无法去掉 id=1 行锁。

KILL 4 有效,直接断开连接。自动回滚正执行线程,释放行锁

第二类:查询慢

mysql> select  * from t where c=50000 limit 1;  c 没有索引,主键顺序扫描 5 万行

为记录到 slow log 里,连接后执行set long_query_time=0时间阈值设置0

图 11 全表扫描 5 万行的 slow log  

11.5 毫秒返回了,超1 秒算慢。坏查询不一定是慢查询

只扫描一行,但执行慢(800 毫秒):mysql> select * from t where id=1;时间都花在哪里了?

图 12 扫描一行却执行得很慢  

select * from t where id=1 lock in share mode(加锁,时间应更长),扫描1 行, 0.2 毫秒。

图 13 加上 lock in share mode 的 slow log  
19 | 为什么我只查一行的语句,也执行这么慢?_第10张图片
图 14 两个语句的输出结果  
19 | 为什么我只查一行的语句,也执行这么慢?_第11张图片
图 15 复现步骤

A 先用 start transaction with consistent snapshot 启动事务, B 才 update 语句。

B 执行完 100 万次 update 语句后,id=1 状态?

19 | 为什么我只查一行的语句,也执行这么慢?_第12张图片
图 16 id=1 的数据状态  (undo log 记录是“把 2 改成 1”,“把 3 改成 2”这样操作逻辑,画成减 1 方便你看图。)

B生成100 万个回滚日志 (undo log)。

lock in share mode 是当前读,直接到 1000001 ,速度快;

select * from t where id=1 一致性读,从 1000001 依次执行 undo log,结果返回。

小结

“查一行”,被锁住和执行慢例子。涉及表锁、行锁一致性读

实际场景更复杂。但大同小异,照定位方法,解决问题。

问题

加锁读:select * from t where id=1 lock in share mode。 id 索引,定位id=1,读锁只加这行。

begin;  select * from t  where c=5 for update;  commit;  怎么加锁的呢?什么时候释放?

只有行锁,执行完,不满足条件行锁去掉。c=5行锁,commit释放

启动配置performance_schema=on,才能用上这个功能,performance_schema库里的表才有数据。

评论1

Read Committed 隔离级别锁聚簇索引记录,只有行锁

Repeatable Read 隔离级别聚簇索引中记录,且锁聚簇索引所有 GAP

设置innodb_locks_unsafe_for_binlog 开启 semi-consistent read 的话,不满足查询条件的记录,MySQL 提前放锁

评论2

begin; select * from t where c=5 for update; commit;

innodb锁全表,返回server层,判断c是否等于5,释放c!=5行锁。

验证方法:

事务A执行(c!=5行):select * from t where id = 3 for update 或者 update t set c=4 where id =3

启动B执行select * from t where c=5 for update; 看有没有被阻塞。B会不会试图锁不满足的。

A、B执行顺序对调,有没有阻塞,B加锁成功会不会释放不满足的行锁。

你可能感兴趣的:(19 | 为什么我只查一行的语句,也执行这么慢?)