为什么我只查询一条语句,查询的还是很慢?
查询长时间不返回
mysql> select * from t where id=1;
情况一: 等MDL锁
可以用show processlist命令查看Waiting for table metadata locak的示意图:
出现这个状态表示的是,现在有一个线程正在表 t 上请求或者持有 MDL 写锁,把 select 语句堵住了。解决办法:找到是谁持有了MDL读锁,然后把他kill掉。但是,由于在 show processlist 的结果里面,session A 的 Command 列是“Sleep”,导致查找起来很不方便。不过有了 performance_schema 和 sys 系统库以后,就方便多了。(MySQL 启动时需要设置 performance_schema=on,相比于设置为 off 会有 10% 左右的性能损失)通过查询 sys.schema_table_lock_waits 这张表,我们就可以直接找出造成阻塞的 process id,把这个连接用 kill 命令断开即可。
情况二:等flush
mysql> select * from information_schema.processlist where id=1;
然后我们用processinglist查看发现
由于一般flush的语句就两种:
flush tables with read lock;
flush tables t with read lock;
这两句话的意思给所有表或者某张表加上读锁,一般用于数据备份。解锁的语句为unlock tables。而且flush正常情况下执行都很快
若出现Waiting for table flush状态的可能情况是:又一个flush tables命令被别的语句堵住了,然后他又把我们的select语句堵住了。解决办法参考情况一。
情况三:等行锁
mysql> select * from t where id=1 lock in share mode;
由于访问 id=1 这个记录时要加读锁,如果这时候已经有一个事务在这行记录上持有一个写锁,我们的 select 语句就会被堵住。
这个问题并不难分析,但问题是怎么查出是谁占着这个写锁。如果你用的是 MySQL 5.7 版本,可以通过 sys.innodb_lock_waits 表查到。查询方法是:
mysql> select * from t sys.innodb_lock_waits where locked_table='test.t'\G
然后采用kill 4 直接断开这个连接。这里隐含的一个逻辑就是,连接被断开的时候,会自动回滚这个连接里面正在执行的线程,也就释放了 id=1 上的行锁。
查询慢
mysql> select * from t where c=50000 limit 1;
有时候查询一行数据也要花费大几百ms,由于字段c没有加索引,导致只走id主键顺序扫描,因此扫描了50000行。
mysql> select * from t where id=1;
发现怎么加了锁速度反而快了?看下输入日志:
第一个语句的查询结果里 c=1,带 lock in share mode 的语句返回的是 c=1000001。
你看到了,session A 先用 start transaction with consistent snapshot 命令启动了一个事务,之后 session B 才开始执行 update 语句。session B 执行完 100 万次 update 语句后,id=1 这一行处于什么状态呢?
session B 更新完 100 万次,生成了 100 万个回滚日志 (undo log)。带 lock in share mode 的 SQL 语句,是当前读,因此会直接读到 1000001 这个结果,所以速度很快;而 select * from t where id=1 这个语句,是一致性读,因此需要从 1000001 开始,依次执行 undo log,执行了 100 万次以后,才将 1 这个结果返回。