内容


关于 DB2 锁升级 (lock escalation) 相关问题的探讨

如何定位发生预期以外的锁升级

Comments

引言

所谓的锁升级(lock escalation),是数据库的一种作用机制,为了节约内存的开销, 其会将为数众多并占用大量资源的细粒度的锁转化为数量较少的且占用相对较少资源的粗粒度的锁,多数情况下主要指将为数众多的行锁升级为一个表锁。 当然,DB2 支持很多粒度的锁,如表空间(table space),表(table),行(row)以及索引(index)等。

正是因为锁升级是一种数据库管理机制,在应用程序层面很难对其进行有效控制。于是在实际应用中,特别是在多并发的场景下,造成很多生产环境中异常状况的锁升级是预期以外的。 笔者曾经遇到过这样一种情况,生产系统由于某些原因,整个处理过程停滞了数分钟。期间客户端一直显示通信中,随后又恢复正常,整个过程没有任何应用程序或中间件异常 Error 被监视到。 也就是说,系统本身并没有任何问题。最后经过多方调查后,确认是由于 DB2 数据库锁升级导致行锁自动升级为表锁,所以原本可以并发的行更新数据库处理被强行转换为串行的更新队列。 由于锁排他的缘故,系统对客户端的响应直到整个更新队列里的更新数据库请求被一条条处理完毕才恢复正常。该问题的定位和解决花费了笔者大量的时间。 为帮助大家尽快确定类似的由锁升级引发的问题,笔者想主要从以下几点为大家介绍如何监视、确认和解决锁升级问题。希望对您能够有所帮助。

  • DB2 锁机制的介绍
  • 什么是 DB2 锁升级 , 什么时候会发生
  • 如果发生锁升级会带来哪些影响
  • 发生了锁升级之后,我们该如何做异常诊断
  • 锁升级导致异常问题确认后,如何解决
  • 一些最佳实践

DB2 锁机制的介绍

DB2 数据库锁从作用类型来分的话基本上有两类 : 排它锁(exclusive lock)记为 X 锁 / 写锁(即,进行写操作的时候加的锁)和共享锁(share lock)记为 S 锁 / 读锁(即,进行读操作的时候加的锁)。这两类锁在起作用时产生以下效果:

  • 排它锁:若事务 T 对数据 D 加 X 锁,则其它任何事务都不能再对 D 加任何类型的锁,直至 T 释放 D 上的 X 锁;一般要求在修改数据前要向该数据加排它锁,所以排它锁又称为写锁。
  • 共享锁:若事务 T 对数据 D 加 S 锁,则其它事务只能对 D 加 S 锁,而不能加 X 锁,直至 T 释放 D 上的 S 锁;一般要求在读取数据前要向该数据加共享锁,所以共享锁又称为读锁。

对于锁的详细介绍,由于篇幅所限且不是本文关注要点,在此不一一列举了,有兴趣的读者可以自行查阅相关资料,笔者在这里不再赘述。

什么是 DB2 锁升级 , 什么时候会发生?

对每一个锁,DB2 数据库都要耗费一定的内存资源来管理并维护(一般情况下,对某个对象上所加的第一个锁需要 256 字节,而在该对象上所加的第二个锁开始就只需要 128 字节了)。 因此,如果在一个表上,有大量的行锁被请求时,为了节约数据库资源的占用,“聪明的”数据库管家会用一个锁住整个表的表锁来代替为数众多的行锁,从而释放了原本大量行锁所占用的资源。 而这个过程,就被称之为锁升级。那么,数据库什么时候会将行锁自动升级为表锁、锁升级遵循怎样的规律、该如何预测锁升级的发生呢? 这里就需要提到两个影响数据库锁升级的 DB2 数据库配置参数:

  • LOCKLIST:分配给锁列表的内存容量。
  • MAXLOCKS:应用程序能够持有的锁列表的最大百分比 , 当任何一个应用程序持有的锁数量达到这个百分比时 , 会选取“行锁最多”的表进行锁升级。

DB2 数据库主要在以下两种情形时会进行锁升级:

  1. 当一个应用的锁所使用的内存〉 LOCKLIST × MAXLOCKS
  2. 多个应用的锁使用的内存〉 LOCKLIST

这时,有读者会有疑问,发生锁升级了,会有什么影响?

如果发生锁升级会带来哪些影响?

掌控之外的事情总是这么令人讨厌,由于这是数据库自行控制的机制,我们在不经意之间享受到了好处的同时,也常常受到该机制的困扰。 除了笔者在文章开头引出的一个真实案例,还有如下一些总结供大家参考,也欢迎大家进行补充。

  1. 降低了系统并发性及性能,显而易见,并行改串行,性能总好不到哪去
  2. 不同事务对于同一张表引发的锁升级会诱发死锁(deadlock)
  3. 在锁升级发生后,由于同表的并发请求被强制转换成串行处理,如果锁等待的时间不是足够长的话,会被数据库“误判”为 lock waiting timeout,从而误导程序员判断问题的根本原因。此时,常常会被认为是由于死锁引起的锁等待时间过长

锁升级的问题,由于一般不在应用程序日志里面进行记录,所以很难被捕获到。有心的读者可以根据上述问题所产生的现象推测出可能是由于数据库产生了锁升级问题而引发的。接下来就是如何确认或者说定位,发生了数据库锁升级。

发生了锁升级之后 , 我们该如何做异常诊断

非常幸运的是,DB2 为我们提供了多种类型的日志以及一些数据库工具来确认和定位类似问题的发生。以下将介绍几种关于数据库锁升级问题的探知方法供大家参考。 各种方法侧重点不一,有的是基于事件捕获型的,有的是基于统计的,读者需要自行判断应该利用哪一种方式。

查看 DB2 实例(instance)级别的数据库通知日志(notification log)

  • 日志路径(默认):${instance_path}/sqllib/db2dump/db2inst1.nfy
  • 预置条件:需要预先打开 snapshot 的 lock monitor;在 DB2 v9.7 之后,可以 set mon_lck_msg_lvl = 1
  • 下图 1 是该通知日志的一个样本示例
图 1. 通知日志中输出的锁升级 log 样例
通知日志中输出的锁升级 log 样例
通知日志中输出的锁升级 log 样例

查看 DB2 数据库(database)级别的数据库诊断日志(diagnosing log)

  • 日志路径(默认):${instance_path}/sqllib/db2dump/db2diag.log
  • 预置条件:需要预先打开 snapshot 的 lock monitor;在 v9.7 之后,可以 set mon_lck_msg_lvl = 1
  • 下图 2 是该诊断日志的一个样本示例
图 2. 诊断日志中输出的锁升级 log 样例
诊断日志中输出的锁升级 log 样例
诊断日志中输出的锁升级 log 样例

利用自带的数据库 snapshot 快照

  • DB2 数据库快照可以用来采集一段时间范围内数据库活动的一些统计信息以及某个时间点数据库的状态信息等
  • 打开监视器:db2 -v update monitor switches using lock on
  • 启用监视器:db2 -v commit / db2 -v terminate
  • 收集快照:db2 -v get snapshot for database on sample | grep -i lock

下面是输出样例

输出样例
 Locks held currently = 21224 
 Lock waits = 24683 
 Time database waited on locks (ms) = 32875 
 Lock list memory in use (Bytes) = 87224 
 Deadlocks detected = 89 
 Lock escalations = 8 
 Exclusive lock escalations = 12 
 Agents currently waiting on locks = 0 
 Lock Timeouts = 0 
 Internal rollbacks due to deadlock = 0

利用数据库事件监听器 event monitor for database 进行监视

DB2 事件监听器 event monitor 的内容比较复杂,所以仅仅提供一些设定脚本供大家参考,需要注意的是利用数据库事件监听器监视数据库锁升级,必须是 for database,因为其它的 event monitor 无法有效捕获相关信息。

设定脚本
 db2 "create event monitor mon_sample_db for database write to file 
	'/tmp/monitor/mon_out_sample_db'"
 db2 commit 
 mkdir /tmp/monitor/mon_out_sample_db 
 db2 "set event monitor mon_sample_db state 1"
查看结果
 db2 "flush event monitor mon_sample_db buffer"
 db2 "set event monitor mon_sample_db state 0"
 db2evmon -path /tmp/monitor/mon_out_sample_db > /tmp/locklog.out 	
 Internal rollbacks due to deadlock = 0

锁升级导致异常问题确认后,如何解决 ?

参照前文所述导致发生锁升级的发生条件中的描述,显而易见我们有如下的方式来尽可能的避免锁升级:

  1. 保持 MAXLOCKS 不变,加大 LOCKLIST 的值:DB2 会增加分配给锁列表的总体内存容量。这样在单个应用程序能够持有的锁列表的最大百分比不变的情况下, 任意一个应用程序在锁升级前能够持有的锁的数量都会有所增加。该配置比较适合系统中有多个应用程序都有可能持有大量行锁的场合。
  2. 保持 LOCKLIST 不变,加大 MAXLOCKS 的值:DB2 不会增加分配给锁列表的总体内存容量,但会增大单个应用程序能够持有的锁列表的最大百分比。 这样某个特定的应用程序在锁升级前能够持有的锁的数量会有所增加。该配置比较适合系统中只有少数的应用程序有可能持有大量行锁的场合。
  3. 同时加大 LOCKLIST 和 MAXLOCKS 的值:DB2 会同时增加分配给锁列表的总体内存容量和增大单个应用程序能够持有的锁列表的最大百分比。 该配置比较适合系统内存容量比较充裕的场合。

由于系统整体内存容量的限制,不可能无限增大上述参数的值(因为调优了这部分锁内存相关的参数之后势必会影响其他内存相关的设置), 所以需要在一个较为合理的范围内控制该参数的取值。篇幅所限,笔者这里就一点而过,有兴趣的读者可以自行研究。此外,适当的加大 LOCKTIMEOUT 的设值可以有效的避免锁等待而导致的超时现象。 毕竟我们都不希望有“Error”关键字出现在我们的系统日志当中。当然 DB2 有自己的回滚机制,不至于会出现业务数据遭到损失的情况。

一些最佳实践

DB2 锁升级旨在通过降低锁的粒度来内存资源的开销,但这也会影响到系统的并发性,因此我们希望能在这两者之间寻求平衡,下面是一些关于锁升级的最佳实践:

  1. 综合考虑及监控系统的运行情况,正确设定或调整 LOCKLIST,MAXLOCKS,LOCKTIMEOUT 等锁相关的参数。 建议通过 DB2 Health Monitor,db2diag.log 或其他的数据库性能检测工具来监控锁升级发生的频率,如果锁升级频繁发生可能是因为 LOCKLIST 配置参数的值对于当前工作负载而言太小, 应该适当增大。如果多个应用程序遇到锁定升级问题,那么可能表明需要增大 LOCKLIST 大小。
  2. 使用高效的 SQL 语句,考虑运行应用程序和 SQL 语句时采用的隔离级别,例如 RR、RS、CS 或 UR。RR 和 RS 隔离级别有可能引起更多的锁定升级, 这是因为锁持有将一直持续到 COMMIT 为止。
  3. 业务逻辑结束后尽快 COMMIT 来释放锁,尽可能的提高 COMMIT 的频率。提高 COMMIT 频率将减少在任意给定时刻挂起的锁定数。 这有助于防止应用程序达到 MAXLOCKS 值,从而在一定程度上避免触发锁升级。
  4. 根据应用程序的特点进行一些修改,如使用 LOCK TABLE 语句来获取表锁定。对于那些并发访问需求并不那么突出的表,这或许是一种好的选择, 当然在选择要锁定的表时须谨慎小心,避免选择并发访问较多的表。
  5. 如果锁升级失败,引起锁升级的应用程序将接到一个 -912 的 SQLCODE,可以在程序里对发生锁升级后程序回滚后重新提交事务。

结束语

本文主要介绍了 DB2 的锁机制以及简单说明了什么是锁升级,并且罗列了几个可能导致锁升级的原因。笔者给出了一个真实生产环境中中碰到的一个实例, 列举了发生锁升级后可能给我们带来的一些困扰之后,为读者提出了四种关于诊断确认锁升级问题的方法。这是本文的重点所在。 在定位了问题之后给出了一些比较通用的解决方式,由于篇幅所限没有展开讨论,有兴趣的读者可以视各自项目的实际情况自行找到解决方法。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=958834
ArticleTitle=关于 DB2 锁升级 (lock escalation) 相关问题的探讨
publish-date=12122013