锁和并发

  本文主要介绍SinoDB数据库锁的粒度与锁类型,如何监视并发控制锁的使用以及使用 Retain Update Lock 功能。

1. 锁和并发性

  为了确保多用户(并发访问)环境中的数据一致性和完整性,数据库服务器必须对要修改的数据进行锁定。

  • 数据库锁粒度
    Database level 数据库锁:对于某些管理活动(如导入和导出)很有用
    Table level 表锁:当更新整个表或表中大多数行时,表级锁非常有用且效率更高
    Page level 页锁:在以物理顺序访问和修改行时可提供锁定效率的最佳效果。
    Row level 行锁:行锁提供最高程度的并发访问,且对OLTP活动最有用。
    Key level 键锁:键锁定与行级锁定一起自动执行,以确保在索引更新期间具有相同的最佳并发级别。

  • 数据库锁类型
    Shared 共享锁
      共享锁可防止其他进程更新锁定的对象。其他用户保持对该对象的读取访问权限。可以在单个对象上放置多个共享锁。
    Exclusive 独占锁
      数据库服务器会自动将独占锁放置在任何正在修改的对象上。独占锁可防止除脏读取之外的所有读取。DBA 或用户还可以显式请求数据库或表上的独占锁以执行管理或批处理操作。
    Update (promotable) 可更新(升级)锁
      可升级锁或更新锁放置在检索以进行更新但尚未更新的对象上,即可以放在已经有SHARED锁的记录,但不能放在已经有PROMOTABLE锁和EXCLUSIVE
    锁的地方。它们可防止其他用户获取对象上的独占或可升级锁。例如,当您使用 FOR UPDATE 子句打开游标时,数据库服务器会在读取的每一行上获取一个更新锁。当执行UPDATE WHERE CURRENT语句时,锁将被提升为独占锁。

2. 数据库级锁

如果你是这样的情况:

  • 执行涉及许多表的大量更新。
  • 归档数据库文件进行备份。
  • 改变数据库的结构

  您可以使用具有EXCLUSIVE选项的DATABASE语句来锁定整个数据库。如上述示例所示。 EXCLUSIVE选项以排它模式打开数据库并只允许当前用户访问数据库。

  要允许其他用户访问数据库,必须执行CLOSE DATABASE语句,然后重新打开数据库。

  具有任何级别数据库权限的用户可以以独占模式打开数据库。这样做不会给他们比平时更高的访问权限。

3. 在共享模式中锁定表

LOCK TABLE customer IN SHARE MODE;

  如果要授予其他用户对表的读取访问权限,但是阻止它们修改其中包含的任何数据,请使用具有IN SHARE MODE选项的LOCK TABLE语句。

  当在SHARE模式下锁定表时,其他用户可以从表中SELECT数据,但是不能在表中INSERT,DELETE或UPDATE行,或者ALTER表。

  在SHARE模式中锁定表不会阻止进程放置行锁来更新。为了避免表上除共享锁外的独占行锁,您必须将表锁定在EXCLUSIVE模式中。*

表锁和事务

  如果您的数据库是日志模式,表锁只允许在事务中。您必须在LOCK TABLE语句之前执行BEGIN WORK语句。

4. 在独占模式中锁定表

LOCK TABLE orders IN EXCLUSIVE MODE;

  如果进程正在修改表中大部分行,则在整个表上放置独占锁可能会很有用。独占表锁阻止用户读取数据,除了Dirty Read隔离级别以外,这可确保其进程也知道正在对数据执行更新。

  另外,由于在表上放置了独占锁,因此数据库服务器可以避免在每个页面或行和修改的键上放置独占锁。如果要修改500万行数据,这将节省大量资源。

独占表锁和存储在BLOBSPACES的简单大对象

  当表以独占模式锁定时,数据库服务器可以对表数据放置的唯一附加锁是对存储在 blob 空间中的简单大对象值上的锁。每个 Blob 页放置两个附加锁。包含简单大对象的表不会获得其他锁。

5. 可配置锁定模式

可配置锁定模式允许您:

  • 全局定义每个会话或每个数据库服务器新创建的表的锁定模式。
  • 无需使用LOCK MODE 子句。

可使用以下来设置:

  • DEF_TABLE_LOCKMODE 配置参数
  • IFX_DEF_TABLE_LOCKMODE 环境变量

  此配置参数由系统管理员在数据库服务器的onconfig配置文件中设置。默认设置是页锁。要覆盖页锁设定,请设置IFX_DEF_TABLE_LOCKMODE环境变量或让系统管理员将默认设置更改为ROW。例如,使用UNIX korn shell,请执行命令:

export IFX_DEF_TABLE_LOCKMODE=ROW

6. 解锁表

UNLOCK TABLE customer;

  在带日志的数据库中,表锁会在事务提交时自动释放。在无日志数据库中,不支持事务,并且表锁一直保持直到进程完成或直到显示执行UNLOCK TABLE语句。

7.锁和页锁

  您决定数据库服务器在创建表或更改表时是否获取页锁或者行和键锁:

CREATE TABLE orders(
 order_num SERIAL NOT NULL,
 customer_num INTEGER,
 order_date DATE)
LOCK MODE ROW;

ALTER TABLE orders LOCK MODE (PAGE);

  创建表时,可以选择访问该表中的任何行时使用的锁定模式。每当需要锁定位于该页面上的单个行时,页锁定都会锁定整个数据页。行锁仅锁定有问题的行。创建表时的默认锁定模式是页锁。

  在事务中,当以表的聚集索引相同的顺序处理行或按物理顺序处理行时,页锁非常有用的。

  在事务中,以任意顺序处理行时,行锁将很有用。

  当锁定的行数变多时,可能有以下风险:

  • 可用锁的数量已耗尽
  • 锁管理的开销变得很大

  这两个锁定级别之间的权衡是页锁定比行锁需要的资源更少,但也降低了并发性。如果在包含许多行的页面上放置页锁,则可能需要访问同一页面其他数据的其他进程可能会被拒绝访问该数据。

8. 设置锁模式

  • 永远等待锁释放

SET LOCK MODE TO WAIT;

  • 不要等待锁释放

SET LOCK MODE TO NOT WAIT;

  • 等待锁释放20秒

SET LOCK MODE TO WAIT 20;

  数据库服务器的默认行为是在SQL请求被现有锁阻止时立即向进程返回一个错误。如果您希望数据库服务器等待锁释放,那么可以使用SET LOCK MODE语句来指定数据库服务器应等待释放锁的时间。

  如果在指定的时间段内锁未被释放,那么该操作失败,并向请求进程返回错误。

  在未指定最大等待时间间隔的情况下,将锁定模式设置为WAIT时请小心。如果您没有为SET LOCK MODE指定等待时间间隔,则理论上进程会永远等待。

9. 保持更新锁

语法:

SET ISOLATION TO DIRTY READ RETAIN UPDATE LOCKS;

SET ISOLATION TO COMMITTED READ RETAIN UPDATE LOCKS;

SET ISOLATION TO CURSOR STABILITY RETAIN UPDATE LOCKS;

  RETAIN UPDATE LOCKS功能是一个开关,可以在与数据库服务器的用户连接期间随时打开和关闭。它只影响具有dirty read,committed read和cursor stability 隔离级别的SELECT … FOR UPDATE语句。

  当在具有上述隔离级别的SELECT … FOR UPDATE语句的FETCH期间更新锁在行上准备就绪时,将不会在后续FETCH或关闭游标时释放。更新锁保持到事务结束。此功能使您避免 repeatable-read可重复读取隔离级别或解决方法(如行上的虚拟更新)的开销。

  要监视会话使用的隔离级别,请使用onstat -g sesonstat -g sql 命令。以下锁定值表示RETAIN UPDATE LOCKS:

描述
DRU Dirty-read with RETAIN UPDATE LOCKS
CRU Committed read with RETAIN UPDATE LOCKS
CSU Cursor stability with RETAIN UPDATE LOCKS

10 .死锁检测

  在某些数据库中,当多个用户在同一资源上请求锁时,可能会发生死锁。死锁是严重的问题,因为它们可以停止数据库系统中的大部分活动。

  SinoDB动态服务器具有内置的复杂机制,可以检测潜在的死锁并防止发生。为了防止发生本地死锁,数据库服务器会维护系统上每个用户的锁列表。在授予锁之前,会检查每个用户的锁列表。如果当前该进程请求锁定的资源上持有锁,那么将识别该锁的所有者并且遍历其锁列表以查看想要新锁的用户是否在等待任何锁。如果是这样,则在该点检测死锁,并向请求锁的用户返回错误消息。

返回的 ISAM 错误代码是:

-143 ISAM error: deadlock detected

  注意:当隔离级别设置未COMMITTED READ LAST COMMITTED, 死锁不会发生。

11. 执行delete操作后发生什么

  由于删除操作没有物理删除关联的索引键,而是将其删除标志设置为1,因此另有一种机制必须物理删除该键项。该机制称为btscanner 线程。

11.1 B-Tree扫描线程如何工作

  删除项目时,设置删除标志。当事务提交时,将删除项目的请求放入名为B-tree扫描池的共享内存池中。该请求是由tblspace number、page number和要删除的key number组成的一个20字节的结构。每个页面只有一个请求被放置在池中。B-tree扫描池以一千字节开始,但是如果此空间变满,则将分配另外1KB给池中以满足更多请求。

  每隔一分钟或者当B-tree扫描池中的请求数超过100,那么 btscanner 线程将唤醒并读取B-tree扫描池中的请求。对于每个请求,btscanner 线程将查找该页面并删除标记为已删除的键。然而,在删除键之前,btscanner 线程通过尝试锁定该行来确保行是已提交的。

11.2 其他会话如何查看已删除的键

  如果另一个会话在读取索引时遇到标识为删除的键,那么会话将检查键值是否仍被锁定。如果是,则该会话假定行仍然存在。但是,如果此行被标记为已删除但未被锁定(B-tree scanner尚未删除该键),那么该会话将跳过键条目,就像不存在一样。