发生的问题
大概在春节期间,由于特殊时期,公司的产品很多无法进展,大量的临时性决策,造成了客服访问量增大,导致了一系列问题,Mysql库居然锁行数,影响行数100W+,下面记录下解决的历程。
排查SQL
原先的SQL大概是这样的
update test
set param1 = 1
where id in (
select id from test
where JoinTime > '2020-03-05'
limit 0,1
)
上面SQL是造成锁表的语句,原本是SQL Server的执行,一直没问题,后来切Mysql,转义过来的。
步骤一
怀疑select可能查询没走索引,导致查询缓慢,进行锁表。
首先,当数据量过大,进行分页时,比如10W条数据,分页条件为 limit 10000, 5
, 那么索引从数据库中查询10005次索引字节,过滤前面的10000条,取最后5个,浪费了大量的时间在这10000次的随机I/O上面,这个时候呢,只能使用select子表
的方式,来避免大量无用的数据进入buffer pool
中,导致Mysql
的性能下降。
改成一下模式:
update test t1
inner join (
select id
from test
where JoinTime > '2020-03-05'
limit 0,1
) t2 on t1.id = t2.id
set param1 = 1
这样,只有1条数据加入buffer pool
,减少buffer pool
的污染,提高效能。
但是,这里咱们没有使用一个大数据量的分页,其次咱们没有引用其他的字段,直接返回身为索引的id
,显然问题不是上面的原因造成的。
参考资料:
步骤二
由于SQL为update
+select
,现在数据库使用的是InnerDB
,是行级锁,当执行update
+select
中的select
进行扫描的时候,update
会把扫描到的数据记录全部锁住,扫描的记录越多,锁住的记录越多,也就是所谓的变扫描边加锁,最后就导致整个数据库的阻塞,程序因为无法进行数据库操作,导致线程池大量阻塞,最后用完,就造成了最后的系统崩溃,业务无法响应。
相当于使用了FOR UPDATE
,将select的结果进行加行级锁。
MyAsim
只支持表级锁,InnerDB
支持行级锁 添加了(行级锁/表级锁)锁的数据不能被其它事务再锁定,也不被其它事务修改、删除 。
是表级锁时,不管是否查询到记录,都会锁定表。
解决
发现问题,我们就可以解决这个,因为我的查询是获取最新的一条数据进行更新,更新也比较简单,所以就直接将查询和更新进行拆分,先进行查询(查询时,如果幂等性要求高时,可以加上排他锁 for update
, 我这里数据没有要求,我就没有加上),然后根据主键进行更新。虽然会造成数据库连接池的浪费,但是避免了大量的数据记录锁住。
如果是多条数据
进行更新,查询出来在进行插入,太复杂,而且性能不高,这个时候就可以使用子表的方式(步骤一中的方式),但是要使用索引
和大数据量的分页
,避免大面积的记录锁定的发生。
of update: 锁定当前行记录,允许其他事物进行读取,但是不允许修改
oracle: 可以使用for update of 进行字段的锁定。
查询的过程中,发现一个有意思的小知识,分享给大家