mysql多线程update死锁问题

最近想起之前处理过的一个mysql 死锁问题,是在高并发下update批量更新导致的,这里探讨一下发生的原因,以及解决办法;

发生死锁的sql语句如下,其中where条件后的字段是有复合索引的。

update t_push_message_device_history set status=?,update_time=? where msg_id=? and msg_key=? and dev_no=?;

之所以会发生死锁,是与mysql 加锁机制有关系的,下面我们来简单描述下加锁过程;

1. mysql锁介绍

  • 表锁:锁住整张表
    表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。

当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。

  • 行锁:锁住一行或者多行
    如果数据库表没有加索引,那么更新操作的时候会锁住整张表,也就是所谓的表级锁;如果有索引,并且查询条件中也带有这些字段,那么就会使用行级锁。行级锁并不是锁记录,而是锁索引。

行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。

虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。

使用行级锁定的主要是InnoDB存储引擎。

2. mysql InnoDB行锁

InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
在实际应用中,要特别注意以下几点:

  • 在不通过索引条件查询的时候,InnoDB使用的是表锁,而不是行锁;

  • 由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的,因为如果不是唯一索引,那么就算是相同的索引键,也可能对应的是不同的记录

  • 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁;

  • 即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

3. mysql InnoDB加锁过程

首先要明确的是,InnoDB是边扫描边加锁的。

因为是边扫描边加锁,这里就存在一个顺序问题,假如线程A对a b c d e五条数据边扫描边加X锁,而同时线程B对 e f g h a五条数据也边扫描边加X锁,明显的,这就会存在一个问题,在线程A对e加锁时,线程B已经对e加锁了,所以线程A会等待线程B释放锁,而线程B对a加锁时,线程A也对a加锁了,所以线程B就会等待线程A释放锁,最终结果是,互相循环等待造成死锁。

4. 解决办法

其实,通过上面的加锁过程,我们很容易就找到解决办法了,那就是where后面条件是主键索引,或者是唯一索引,唯一索引或者主键索引不会出现这种问题,是因为每次都只有一条数据,线程A加锁后,线程B等待即可。

  • 如果where后面条件是主键索引,或者是唯一索引,那么直接就对主键行加X锁;

  • 如果是二级索引,边扫描边对二级索引行加X锁及间隙加GAP锁,然后再根据二级索引里的主键信息去扫描聚簇索引对主键行依次加X锁;

你可能感兴趣的:(mysql多线程update死锁问题)