天天看点

MySQL · 答疑解惑 · MySQL 锁问题最佳实践

最近一段时间处理了较多锁的问题,包括锁等待导致业务连接堆积或超时,死锁导致业务失败等,这类问题对业务可能会造成严重的影响,没有处理经验的用户往往无从下手。下面将从整个数据库设计,开发,运维阶段介绍如何避免锁问题的发生,提供一些最佳实践供rds的用户参考。

在数据库设计阶段,引擎选择和索引设计不当可能导致后期业务上线后出现较为严重的锁或者死锁问题。

1. 表引擎选择使用myisam,引发table level lock wait。

从5.5版本开始,mysql官方就把默认引擎由myisam转为innodb,这两种引擎的主要区别:

MySQL · 答疑解惑 · MySQL 锁问题最佳实践

由于myisam引擎只支持table lock,在使用myisam引擎表过程中,当数据库中出现执行时间较长的查询后就会堵塞该表上的更新动作,所以经常会碰到线程会话处于表级锁等待(waiting for table level lock)的情况,严重的情况下会出现由于实例连接数被占满而应用无法正常连接的情况

从上述的案例中可以看到,t_myisam表为myisam存储引擎,当该表上有执行时间较长的查询语句在执行的时候,该表上其他的更新全被堵塞住了,这个时候应用或者数据库的连接很快耗完,导致应用请求失败。这个时候快速的恢复方法为将线程id:111 kill掉即可(可以执行show processlist查看到当前数据库所有连接状态)。另外myisam存储引擎的表索引在实例异常关闭的情况下会导致索引损坏,这个时候必须要对表进行repair操作,该操作同样会阻塞该表上的所有请求。

2. 表索引设计不当,导致数据库出现死锁。

索引设计是数据库设计非常重要的一环,不仅仅关系到后续业务的性能,如果设计不当还可导致业务上的死锁。下面的一则案例就出现在线上系统,数据库在并发更新的时候出现了死锁,通过排查定位于update更新使用了两个索引导致,死锁信息如下:

表结构:

sql执行计划:

所以第一个事务先根据group_id索引,已经锁住primary id,然后再根据test_id索引,锁定primary id;第二个事务先根据test_id索引,已经锁住primary id,然后再根据group_id索引,去锁primary id;所以这样并发更新就可能出现死索引。

加test_id + group_id的组合索引,这样就可以避免掉index merge;

将优化器的index merge优化关闭。

事务处理时间过长,导致并发出现锁等待。

并发事务处理在数据库中经常看到的应用场景,在这种场景下,需要避免大事务,长事务,复杂事务导致事务在数据库中的运行时间加长,事务时间变长则导致事务中锁的持有时间变长,影响整体的数据库吞吐量。下面的一则案例中,用户的业务数据库中出现大量的update等待,导致大量的业务超时报错:

MySQL · 答疑解惑 · MySQL 锁问题最佳实践

问题排查:

通过<code>show processlist</code>确定出现锁等待的线程以及sql信息:

通过innodb的information_schema数据库中的锁等待以及事务试图,查出相关的锁信息:

从述的锁等待信息中发现,事务26432631被26432630阻塞了,那么我们就可以从general log中去排查一下事务26432630做了哪些操作。

从提前打开数据库的general log中查找到上述被blcok 的update语句在日志中的位置,发现了update被blcok的根本原因: 日志中有两条不同的sql同时并发更新同一条记录,所以后更新的sql会等待前更新的sql,如果sql1所在的事务1直没有提交,那么事务2将会一直等待,这样就出现上述updating的状态

所以我们就可以在看一下这个事务的上下文:

thread id=235202017 的sql上下文:

thread id=318984063 的sql上下文:

可以看到事务1 从13:54:39开始,直到13:55:30结束,事务2 中有更新事务1中的同一条记录,所以直到事务1 提交后,事务2才得以执行完毕,有了这样的日志,将此信息发给用户很快就找到了问题,在事务1中由于还存在其他的业务逻辑,导致事务1的提交迟迟没有完成,进而导致了其他业务锁的发生。

ddl操作被大查询block。

当应用上线进入维护阶段,则开始会有较多的数据库变更操作,比如:添加字段,添加索引等操作,这一类操作导致的锁故障也是非常频繁的,下面将会介绍一则案例,一个ddl操作被查询block,导致数据库连接堆积:

metadata lock wait 的含义:为了在并发环境下维护表元数据的数据一致性,在表上有活动事务(显式或隐式)的时候,不可以对元数据进行写入操作。因此 mysql 引入了 metadata lock ,来保护表的元数据信息。因此在对表进行上述操作时,如果表上有活动事务(未提交或回滚),请求写入的会话会等待在 metadata lock wait。

导致 metadata lock wait 等待的常见因素包括:活动事务,当前有对表的长时间查询,显示或者隐式开启事务后未提交或回滚,比如查询完成后未提交或者回滚,表上有失败的查询事务等。

上述案例中,查询,更新和ddl操作的线程状态都为waiting for table metadata lock,对表t的操作全部被阻塞,前端源源不断的请求到达数据库,这个时候数据库的连接很容易被打满,那我们来分析一下为什么有这这些锁等待:

alter 操作的锁等待:由于在表t上做了一个添加字段的操作,该操作会在结束前对表获取一个metadata lock,但是该表上已经有一个查询一直未结束,导致metadata lock一直获取不到,所以添加字段操作只能等待查询结束,这就解释了alter操作为什么状态为waiting for table metadata lock。

查询和更新的锁等待:由于前面进行的alter操作已经在t表上试图获取metadata lock,所以后续对表t的的查询和更新操作在获取metadata lock的时候会被alter操作所阻塞,进而导致这些线程状态为waiting for table metadata lock。

解决办法则是将线程6 kill 掉即可。

锁问题是非常常见的问题,需要我们在数据库开发、设计、管理的各个阶段都需要注意,防范未然,做到心中有数。

设计开发阶段:

表设计要避免使用myisam存储引擎,改用innodb引擎;

为sql创建合适的索引,避免多个单列索引执行出错;

避免大事务,长事务,复杂事务导致事务在数据库中的运行时间加长。

管理运维阶段:

在业务低峰期执行上述操作,比如创建删除索引;

在结构变更前,观察数据库中是否存在长时间运行的sql,未提交的事务;

结构变更期间,监控数据库的线程状态是否存在lock wait。