MySQL 本身压力大, CPU 占用率高或IO 利用率高。字段 id 和 c,10 万行记录:
第一类:查询长时间不返回
mysql> select * from t where id=1;
表 t 被锁住。show processlist 。针对状态,分析原因、如何复现,以及如何处理。
1.1等 MDL 锁
Waiting for table metadata lock:一个线程在表 t 上请求或持有 MDL 写锁,把 select 语句堵住了。
在第 6 篇文章《全局锁和表锁 :给表加个字段怎么有这么多阻碍?》中, MySQL 5.7 版本修改 MDL 加锁策略,不能复现这个场景了。5.7 版本下复现也很容易:
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 命令断开连接
1.2等flush
表 t 上mysql> select * from information_schema.processlist where id=1;
线程状态Waiting for table flush,设想原因
表示:线程对表 t 做 flush。 flush 操作用法:
指定表 t 只关闭表 t:flush tables t with read lock;
关闭所有打开表:flush tables with read lock;
都快,除非被堵住。
flush tables 命令被别的语句堵住了,它又堵住 select 。复现
A每行都调用sleep(1),默认执行 10 万秒, t 一直被 A打开。 B flush tables t 关闭 t,需等 A结束。C被 flush 命令堵住
1.3、等行锁
mysql> select * from t where id=1 lock in share mode; 第 8 篇《事务到底是隔离的还是不隔离的?》
访问 id=1 加读锁,如已有事务在这行上写锁,select 被堵。
session 占写锁,不提交,B 被堵住的原因。
5.7 版本sys.innodb_lock_waits 表查谁占写锁
mysql> select * from t sys.innodb_lock_waits where locked_table=`'test'.'t'`\G
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 毫秒返回了,超1 秒算慢。坏查询不一定是慢查询。
只扫描一行,但执行慢(800 毫秒):mysql> select * from t where id=1;时间都花在哪里了?
select * from t where id=1 lock in share mode(加锁,时间应更长),扫描1 行, 0.2 毫秒。
A 先用 start transaction with consistent snapshot 启动事务, B 才 update 语句。
B 执行完 100 万次 update 语句后,id=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加锁成功会不会释放不满足的行锁。