什么是锁升级

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

对每一个锁,DB2 数据库都要耗费一定的内存资源来管理并维护。因此,如果在一个表上,有大量的行锁被请求时,为了节约数据库资源的占用,“聪明的”数据库管家会用一个锁住整个表的表锁来代替为数众多的行锁,从而释放了原本大量行锁所占用的资源。 而这个过程,就被称之为锁升级。

一句话总结:细粒度的锁占用了太多的系统资源,需要释放资源

锁升级涉及到的配置

属性LOCKLIST:分配给锁列表的内存容量。

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

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

当一个应用的锁所使用的内存〉 LOCKLIST × MAXLOCKS

多个应用的锁使用的内存〉 LOCKLIST

带来的影响

1、降低了系统并发性及性能,显而易见,并行改串行,性能总好不到哪去

2、不同事务对于同一张表引发的锁升级会诱发死锁(deadlock)

3、在锁升级发生后,由于同表的并发请求被强制转换成串行处理,如果锁等待的时间不是足够长的话,会被数据库“误判”为 lock waiting timeout,从而误导程序员判断问题的根本原因。此时,常常会被认为是由于死锁引起的锁等待时间过长

如何查看/跟踪?

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

1、日志路径(默认):
${instance_path}/sqllib/db2dump/db2inst1.nfy

2预置条件:需要预先打开snapshot 的 lock monitor;在 DB2v9.7 之后,可以 set mon_lck_msg_lvl = 1

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

1、日志路径(默认):
${instance_path}/sqllib/db2dump/db2diag.log

2、预置条件:需要预先打开snapshot 的 lock monitor;在 v9.7 之后,可以 set mon_lck_msg_lvl = 1

利用自带的数据库 snapshot 快照

1、DB2 数据库快照可以用来采集一段时间范围内数据库活动的一些统计信息以及某个时间点数据库的状态信息等

2、打开监视器:db2-v update monitor switches using lock on

3、启用监视器:db2-v commit / db2 -v terminate

4、收集快照:db2 -vget snapshot for database on sample | grep -i lock

解决方案

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

2、保持 LOCKLIST不变,加大 MAXLOCKS 的值:DB2 不会增加分配给锁列表的总体内存容量,但会增大单个应用程序能够持有的锁列表的最大百分比。这样某个特定的应用程序在锁升级前能够持有的锁的数量会有所增加。该配置比较适合系统中只有少数的应用程序有可能持有大量行锁的场合。

3、同时加大LOCKLIST 和 MAXLOCKS 的值: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,可以在程序里对发生锁升级后程序回滚后重新提交事务。