数据库行锁争用问题分析

发表于:2021-2-09 09:27

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:童薇    来源:51Testing软件测试网原创

  某系统的批量程序和联机程序同时发起交易、操作数据库同一张表时,出现了交易间相互影响导致交易失败。
  分析原因为事务A对数据库表操作全表删除时,数据库机制会自动触发产生排他行锁,当行锁数达到“升级到表锁前允许的最大行锁数”时,会自动升级为表锁。但因事务B已对数据库表产生了排他行锁,则事务A自动升级表锁失败。直至事务A消耗掉了数据库“最大锁资源数”,系统会报错“锁资源耗尽”、交易失败。
  因此,设计程序时,如涉及多事务操作数据库同一张表,需考虑事务间的关联影响,是否会引发事务冲突。
  下文将详细描述和分析该问题。
  1 数据库锁
  首先介绍一下数据库锁的相关概念。
  1.1 行锁、页锁和表锁
  行锁是访问数据库的时候,锁定一行或者多行记录。页锁是访问数据库的时候,锁定查询结果所在页。表锁是访问数据库的时候,锁定整张表,在表被锁定期间,其他事务不能对该表进行操作,必须等当前表的锁被释放后才能进行操作。行锁的优点是锁定粒度小,发生锁冲突的概率低,并发度高;缺点是开销大,加锁慢,会出现死锁。表锁的优点是开销小,加锁快,不会出现死锁;缺点是锁定力度大,发生锁冲突概率高,并发度最低。页锁的开销和加锁速度介于表锁和行锁之间,会出现死锁,锁定粒度介于表锁和行锁之间,并发度一般。
  共享锁 (S锁) 也称为读锁,用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。其封锁粒度包括行级、页级和表级。
  排他锁(X锁)也称为独占锁、写锁,用于数据修改操作,例如INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突)。其封锁粒度也包括行级、页级和表级。
  1.2 意向锁
  意向锁的产生原因:解决表锁与之前可能存在的行锁冲突,避免为了判断表是否存在行锁而去扫描全表的系统消耗。
  作用:一种快速判断表锁与之前可能存在的行锁冲突的机制。(数据库会帮我们自动加)
  行锁在加锁前要先加意向锁。意向锁是一种表锁。
  事例分析:事务 A 锁住了表中的一行,让这一行只能读,不能写。之后,事务 B 申请整个表的写锁。如果事务 B 申请成功,那么理论上它就能修改表中的任意一行,这与 A 持有的行锁是冲突的。
  数据库需要避免这种冲突,就是说要让 B 的申请被阻塞,直到 A 释放了行锁。数据库要怎么判断这个冲突呢?
  step1:判断表是否已被其他事务用表锁锁表。
  step2:判断表中的每一行是否已被行锁锁住。
  注意step2,因为需要遍历整个表,从而导致判断方法的效率不高。为了更有针对性的进行判断,便产生了意向锁的概念。
  在意向锁存在的情况下,事务 A 必须先申请表的意向共享锁,成功后再申请一行的行锁。
  在意向锁存在的情况下,上面的判断可以改成:
  step1:不变。
  step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务 B 申请表的写锁会被阻塞。
  意向锁分为意向共享锁(共享锁定)和意向排他锁(排他锁定),且意向锁是表级别锁。
  例子-执行update:
  1> update PD set workdate = '20200603' where PDid ='23'
  2> go
  (1 row affected)
  1> sp_lock3x
  2> go
  返回结果:
  解释:
  Sh--共享锁(S锁)
  Ex--独占锁/排他锁(X锁,Exclusive Lock)
  Update--更新锁(U锁)
  Table或者intent--锁发生在表
  Page--锁发生在表
  Row--锁发生在行
  blk--这个进程正在阻塞另一个需要获取一个锁的进程,一旦这个进程处理完成,其他进程就可以继续处理了
  demand--这个进程正在试图获取一个锁
  例子-未执行update前:
  1> sp_lock3x
  2> go
  返回结果:
  1.3触发机制
  所有关系型数据库都存在行锁,不同数据库触发机制大致相同,只是手工生成行锁方法略有不同。
  MySQL数据库,不同的存储引擎支持不同的锁机制。MyISAM和MEMORY存储引擎采用的是表级锁;BDB存储引擎采用的是页面锁,但也支持表级锁;InnoDB存储引擎既支持行级锁,也支持表级锁,但默认情况下是采用行级锁。以InnoDB存储引擎举例,意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他行锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享行锁或排他行锁。
  共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。
  排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。
  Oracle数据库,行锁在操作DML(UPDATE、DELETE、INSERT)语句时,Oracle会自动加上行锁。在select * from table for update 【of column】【nowait|wait 3】时,oracle也会自动加锁;但在缺省情况下,单纯地读数据(SELECT)并不加锁,Oracle通过回滚段来保证用户不读“脏”数据。
  参数【nowait|wait 3】,一般在for update 时加nowait,这样就不用等待其他事务执行了,一判断有事务,立马抛出错误。
  参数【of column】,一般的of column都是在多表操作的时候,能锁定其中一个表的某些列,如果还是同一张表的话,并不能锁定某些列。
  Sybase数据库,支持行锁、页锁、表锁。意向锁是自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,会自动给涉及数据集加排他行锁(X);对于普通SELECT语句,不会加行锁。
  2 现象描述
  测试环境上某系统运行批量程序时,报错“锁资源耗尽”。
  经分析,产生原因为测试环境上跑批量时,准备清理LSB表中小于当日的数据,Sybase数据库执行SQL语句“EXEC SQL delete FROM LSB WHERE appdate < :Workdate”,数据量约300万条。而当时正在同时发起实时联机交易,对LSB表生成了意向排他锁和排他行锁未释放,该批量执行delete语句需使用300w个行锁(锁资源最大可使用100w个)且因排他行锁存在无法升级使用表锁,导致批量失败,报错“锁资源耗尽”。
  3 原因分析
  A:对LSB表执行update语句
  B:EXEC SQL delete FROM LSB WHERE appdate < :Workdate
  C:EXEC SQL delete top 4000 FROM LSB
  WHERE appdate < :Workdate
  当执行A操作时,数据库对LSB先产生一个意向排他锁,再产生一个排他行锁。当执行B操作时,删除300w条记录,会产生300w条行锁(行锁大于5k条,曾试图升级表锁),但发现LSB表存在意向排他锁,升级行锁失败,只能使用行锁(行锁资源上限为100w条),B交易报错“锁资源耗尽”。
  另外,测试环境上很少出现“锁资源耗尽”现象,分析原因为意向排他锁是瞬时的、转瞬即逝的,很快就消失,所以非常偶发。只有当A的排他意向锁与B的行锁升级为表锁的时刻碰撞上,才会出现上述现象。
  当将B方案优化为C方案时,控制每次产生行锁的条数小于5k,避免升级为表锁,这样利于A(前提是A的tps小于500)和C同时操作且互不影响。
  4 应对措施
  通过将“流水表批量清理交易”优化为先查找top 1的记录,若记录存在则delete前top 4000条记录,循环执行,直至小于当日的数据全部清理。
  5 测试验证
  测试环境的数据库配置参数和铺底数据如下:
  查看数据库锁资源大小
  1> sp_configure "number of locks"
  2> go
  返回结果:
  测试发现,当测试环境模拟实时联机交易持续运行(TPS为4-46笔/秒)时,新、旧程序均执行成功,CPU使用率无差异,无死锁,新程序的执行时间长于旧程序。但是,交易背景未复现该问题。
  当测试环境模拟对LSB表执行update语句、但事务不提交、持续产生排他意向锁和排他行锁的场景后,旧程序运行不到1s,报错“锁资源耗尽”,交易失败,复现该问题。而新程序运行30s,交易成功,锁资源使用数约为4000-4010个。
  执行SQL语句,但不提交:
  1> begin transaction
  2> go
  1> update PD set workdate = '20200602' where PDid ='23'
  2> go
  (1 row affected)
  1>
  执行SQL语句,且提交:
  1> begin transaction
  2> go
  1> update PD set workdate = '20200602' where PDid ='23'
  2> go
  (1 row affected)
  1> commit
  2> go
  1>
  因此,当实时联机交易对LSB表生成了排他意向锁未释放时,旧程序复现该问题、交易成功率为0%,新程序执行成功、交易成功率为100%,新程序的清理机制优于旧程序的清理机制。测试通过。
  WHERE appdate < :Workdate
  1、交易背景“update语句,但事务不提交,持续产生排他意向锁和排他行锁”下,旧程序报错。
  2、交易背景“update语句,但事务不提交,持续产生排他意向锁和排他行锁”下,新程序交易成功。
  6 问题启示
  程序设计时,需考虑多支交易对数据库(包括MySQL、Oracle、Sybase等)同一张表产生排他行锁是否相互影响的场景。特别是出现删除大量数据等操作,如其他交易也对该表产生排他行锁,建议控制事务大小,如每次的删除记录条数,避免行锁升级表锁失败从而导致交易报错。
  还需考虑,两个交易均需更新数据库同一张表的记录时,程序均需先锁表,再进行下一步操作,如顺序颠倒易出现交易失败。

      版权声明:本文出自51Testing会员投稿,51Testing软件测试网及相关内容提供者拥有内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像,否则将追究法律责任。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号