GBase 8s SQL 指南:教程———11 对多用户环境编程

本部分描述当您在多用户环境中工作时需要注意的几个编程问题。

如果您的数据库包含在单个用户工作站中,且不访问来自另一计算机的数据。则您的程序 可任意修改数据。在所有其他情况下,您必须考虑一种可能性,即,在您的程序正在修改 数据时,另一程序正在读取或修改同一数据。将这种情况描述为并发:同一时刻对相同数 据的两处或多处独立的使用。本部分讨论并发、锁定和隔离级别。

本部分还描述语句高速缓存特性,它可减少每一会话的内存分配,并加速查询处理。语句 高速缓存存储那些稍后在使用相同的SQL语句的不同的用户会话之中共享的数据。

11.1并发和性能

在多编程系统中,要有好的性能,并发至关重要。为了保证某一时刻仅一个程序可使用数 据,当访问序列化的数据时,处理会显著减慢。

11.2锁定和完整性

除非对数据的使用作出控制,否则,并发可导致许多负面效果。程序可读取过时的数据, 或可丢失所做的修改,即使表面上已经完成了它们。

要防止此类错误,数据库服务器强加一个锁定系统。锁定是程序可在数据块上放置的声明 或保留。只要锁定数据,数据库服务器保证没有其他程序可修改它。当另一程序请求该数 据时,数据库服务器或者让该程序等待,或者让其返回并报错。

11.3锁定和性能

由于锁定序列化对一块数据的访问,因此,它减少并发;任何想要访问该数据的其他程序 都必须等待。数据库服务器可在单个行、磁盘页、整个表或整个数据库上放置锁。(磁盘 页可能保存多行,且一行可能需要多个磁盘页。)它越是放置锁,它锁定的对象越大,并 发降得越低。锁越少,锁定的对象越小,并发和性能越高。

下列部分讨论您可如何使您的程序实现下列目标:

•放置所有必要的锁以确保数据完整性。

•锁定与前面的目标可能相一致的最少、最小的数据块。

11.4并发问题

要理解并发的危险性,您必须从多个程序的方面考虑,每一程序以它自己的速度执行。假 设您的程序通过下列游标正在访存行:

EXEC SQL DECLARE sto_curse CURSOR FOR

SELECT * FROM stock

WHERE manu_code = ‘ANZ’;

将每一行从数据库服务器传送到程序都花费时间。在传送期间和传送之间,其他程序可执 行其他数据块操作。大约在您的程序访存由那个查询产生的行的同一时刻,另一用户的程 序可能执行下列更新:

EXEC SQL UPDATE stock

SET unit_price = 1.15 * unit_price

WHERE manu_code = ‘ANZ’;

换句话说,两个程序都在通读同一个表,一个正在访存某些行,而另一个正在更改相同的 行。可能出现下列情况:

在您的程序访存它的第一行之前,其他程序完成了它的更新。
您的程序仅向您显示更新了的行。

在其他程序有机会更新它之前,您的程序访存了每行。
您的程序仅向您显示原始的行。

在您的程序访存某些原始的行之后,其他程序赶上并继续更新您的程序还要读取的 一些行;然后,它执行COMMIT WORK语句。
您的程序可能返回的既有原始的行,也有更新了的行。

与第3种情况一样,除了在更新表之后,另一程序发出ROLLBACK WORK语句 之外。
您的程序可向您显示的既有原始的行,也有在数据库中不再存在的更新了的行。

前两种可能性是无害的。在第1种可能的情况中,在您的查询开始之前完成了更新。更新 是在一毫秒之前结束的,还是一周前结束的,并无差异。

在第2种可能的情况中,实际上,您的查询在更新开始之前完成。其他程序可能紧跟在您 的程序之后只处理了一行,或它可能直到明晚才开始;这没有关系。

然而,后两种可能的情况对于某些应用程序的设计可至关重要。在第3种可能的情况下, 查询返回的既有更新了的数据,也有原始数据。在某些应用程序中,那种结果可能是有害 的。在其他程序中,诸如计算所有价格的平均值,根本无关紧要。

由于取消了它们的事务,如果程序返回一些在表中不可再找到的数据行,则第4种可能的 情况是灾难性的。

当您的程序使用游标来更新或删除最后访存的数据时,需要引起另外的关注。下列的事件 序列产生错误的结果:

•您的程序访存该行。

•另一程序更新或删除该行。

• 您的程序更新或删除 WHERE CURRENT OF cursor_name。

要控制诸如此类的事件,请使用数据库服务器的锁定和隔离级别特性。

11.5锁定如何工作

GBase 8s数据库服务器支持复杂的、灵活的锁定特性的集合,本部分中描述这些主题。

    1. 1锁的种类

下表展示GBase 8s数据库服务器对不同情况支持的锁的类型。

锁类型

用途

共享的

共享锁为了只读保留它的对象。它防止在该锁保留期间更改该对象。多个程序可 在同一对象上放置共享锁。在以共享的模式锁定记录时,多个对象可读取该记录。

排他的

排他锁为了单个程序的使用保留它的对象。当程序打算更改该对象时,使用此锁。 您不可在存在任何其他种类锁的地方放置排他锁。在您放置排他锁之后,您不可 在同一对象上放置另一锁。

可提升 的(或 更新)

可提升的(或更新)锁有更新的意图。您仅可在不存在其他可提升锁或排他锁的 地方放置它。您可在已有共享锁的记录上放置可提升锁。当程序要更改该锁定了 的对象时,您可将该可提升锁提升为排他锁,但仅当该锁可能从可提升的更改为 排他的时刻,该记录上没有包括共享锁在内的其他锁时。如果当设置了可提升锁 时,共享锁在该记录上,则在将可提升锁提升为排他锁之前,您必须删除共享锁。

  1. 5.2锁作用域

您可将锁应用于整个数据库、整个表、磁盘页、单个行或索引键值。正被锁定的对象的大 小称之为锁的作用域(也称为锁颗粒度)。通常,锁的作用域越大,并发降得越低,但编 程越简单。

数据库锁

您可锁定整个数据库。打开数据库的操作在数据库的名称上放置一共享锁。使用

CONNECT, DATABASE或CREATE DATABASE语句打开数据库。只要程序有一数据 库是打开的,则该名称上的共享锁防止任何其他程序删除该数据库或在其上放置排他锁。

下列语句展示您可能如何排他地锁定整个数据库:

DATABASE database_one EXCLUSIVE

如果没有其他程序已打开了那个数据库,则此语句成功。放置该锁之后,其他程序不可打 开该数据库,即使是读取也不可,因为它在该数据库名称上放置共享锁的尝试会失败。

仅当关闭该数据库时,才释放数据库锁。可使用DISCONNECT或CLOSE DATABASE语 句来显式地执行那个操作,或通过执行另一 DATABASE语句来隐式地执行。

由于锁定数据库导致那个数据库中的并发降低为零,因此,它使得编程非常简单;不可发 生并发效果。然而,仅当其他程序不需要访问时,才应锁定数据库。在非高峰期间,对数 据进行大量更改之前,通常使用数据库锁定。

表锁

您可锁定整个表。在某些情况下,数据库服务器自动地执行此操作。您还可使用LOCK TABLE语句来显式地锁定整个表。

LOCK TABLE语句或数据库服务器可放置下列类型的表锁:

共享锁

任何用户都不可写表。在共享模式下,数据库服务器在表上放置一个共享锁,其通 知其他用户不可执行更新。此外,数据库服务器为每个更新了的、删除了的或插入 了的行添加锁。

排他锁

任何其他用户不可从该表读取或写该表。在排他模式下,数据库服务器仅在该表上 放置一个排他锁,不论它更新多少行。排他的表锁防止该表的任何并发使用,因此, 如果其他程序正在争夺对该表的使用,则可严重影响性能。然而,当您需要更新表 中的大部分行时,请在表上放置排他锁。

使用LOCK TABLE语句来锁定表

事务告诉数据库服务器通过LOCK TABLE语句来使用表级别锁定。下列示例展示如何在 表上放置排他锁:

LOCK TABLE tab1 IN EXCLUSIVE MODE

下列示例展示如何在表上放置共享锁:

LOCK TABLE tab2 IN SHARE MODE

提示:在提供更大的并发时,您可为数据库服务器设置隔离级别来获得与共享的表锁相同 的保护程度。

数据库服务器何时自动地锁定表

在数据库服务器对任何下列语句执行操作时,它总是锁定整个表:

ALTER FRAGMENT

ALTER INDEX

ALTER TABLE

CREATE INDEX

DROP INDEX

RENAME COLUMN

RENAME TABLE

完成该语句(或事务结束)会释放该锁。在某些查询期间也可自动地锁定整个表。

使用ONLINE关键字来避免表锁定

对于未以IN TABLE关键字选项定义的索引,当您使用ONLINE关键字来CREATE或 DROP索引时,您可最小化索引了的表上的排他锁的持续时间。

在联机创建或删除索引时,不支持表上的DDL操作,但当发出了 CREATE INDEX或 DROP INDEX语句时,可完成并发了的操作。直到没有其他进程正在并发地访问该表时, 才创建或删除指定的索引。然后,短暂地保持锁来写与该索引相关联的系统目录数据。这 提高了系统的可用性,因为通过正在进行的会话或新会话仍可读该表。下列语句展示如何 使用ONLINE关键字来避免在使用CREATE INDEX语句时的自动表锁定:

CREATE INDEX idx_1 ON customer (Iname) ONLINE;

然而,对于使用IN TABLE关键字选项定义的“表中”索引,在包括ONLINE关键字的 CREATE INDEX或DROP INDEX操作期间,该索引了的表保持锁定。其他会话尝试访问 该锁定了的表时,会失败并发出这些错误之一:

107: ISAM error: record is locked.

211: Cannot read system catalog (systables).

710: Table (table.tix) has been dropped, altered or renamed.

行和键锁

您可锁定表的一行。在其他程序继续处理同一表的其他行时,程序可锁定一行或选择的多 行。

行和键锁定不是缺省的行为。当您创建表时,您必须指定行级别锁定。下列示例创建带有 行级别锁定的表:

CREATE TABLE tab1

(

col1…

)LOCK MODE ROW;

当您创建表时,如果指定LOCK MODE子句,则您可稍后使用ALTER TABLE语句来更 改锁定模式。下列语句将保留表上的锁定模式更改为页级别锁定:

ALTER TABLE tabl LOCK MODE PAGE

在某些情况下,数据库服务器必须锁定不存在的行。要这样做,数据库服务器在索引键值 上放置一个锁。使用键锁与使用行锁一样。当表使用行锁定时,作为假想行上的锁来实现 键锁。当表使用页锁定时,在包含该键或如果存在则包含该键的索引页上放置一键锁。

当您插入、更新或删除键(当您插入、更新或删除行时,自动地执行)时,数据库服务器 在该索引的键上创建锁。

当您更新较少行数时,由于它们提高并发性,因此行和键锁通常提供最佳性能。然而,数 据库服务器在获取锁时会造成一些开销。

当通过排他锁来锁定表中的一行或多行时,对其他用户的影响部分地依赖于它们的事务隔 离级别。其隔离级别不是Dirty Read的其他用户可能遇到事务失败,这是由于在指定的时 限内,未释放了排他锁。

对于Committed Read或Dirty Read隔离级别操作,这些操作尝试访问那些并发的会话已 在其上设置了排他的行级别锁的表,为了降低锁定冲突的风险,可启用事务来读取锁定了 的行中数据的最近提交的版本,而不是等待提交或回滚设置该锁的那个事务。启用对排他 地锁定了的行的最后提交的版本的访问,可以采用几种方式来实现:

对于个别的会话,发出此SQL语句
SET ISOLATION TO COMMITTED READ LAST COMMITTED;

对于使用Committed Read或Read Committed隔离级别的所有会话,DBA可将 USELASTCOMMITTED 配置参数设置为’ALL’或设置为’COMMITTED READ’。
对于使用Committed Read或Read Committed隔离级别的个别的会话,任何用户 都可发出带有’ALL’或’COMMITTED READ’的 SET ENVIRONMENT USELASTCOMMITTED语句作为此会话环境选项的值。
对于使用Dirty Read或Read Uncommitted隔离级别的所有会话,DBA可将
USELASTCOMMITTED配置参数设置为’ALL’或设置为’DIRTY READ’。
对于使用Dirty Read或Read Uncommitted隔离级别的个别的会话,任何用户都可 发出带有 ‘ALL’ 或 ‘DIRTY READ’ 的 SET ENVIRONMENT USELASTCOMMITTED 语句作为此会话环境选项的值。
仅当行级别锁定生效时,而不是当另一会话持有对整个表的排他锁时,此LAST COMMITTED才有用。对于任何LOCK TABLE语句在其上应用表级别锁的任何表,禁用 此特性。请参阅《GBase 8s SQL指南:语法》中的SET ENVIRONMENT语句的描述, 要获取关于对并发访问通过排他锁锁定其中一些行的表的此特性的更多信息,以及对于可 支持此特性的表的种类限制的更多信息,请参阅《GBase 8s管理员参考手册》中的 USELASTCOMMITTED 配置参数的描述。

页锁 数据库服务器以称为磁盘页的单位存储数据。一个磁盘页包含一行或多行。在一些情况下, 最好锁定一个磁盘页,而不是锁定它上面的个别行。例如,对于要求更改大量行的操作, 您可能选择页级别锁定,因为行级别锁定(每行一锁)的性价比可能不高。

当您创建表时,如果您未指定LOCK MODE子句,则对于该数据库服务器的缺省行为为 页级别锁定。使用页锁定,数据库服务器锁定包含该行的整个页。如果您更新存储在同一 页上的若干行时,数据库服务器对该页仅使用一个锁。

为所有CREATE TABLE语句设置行锁或页锁模式

对于所有新创建的表,GBase 8s允许您为单个用户(每会话)或为多个用户(每服务器) 将锁模式设置为页级别锁定或行级别锁定。您不再需要在每次使用CREATE TABLE语句 创建新表时指定锁模式。

如果您想要以特定的锁模式创建您的会话内的每个新表,则您必须设置 IFX_DEF_TABLE_LOCKMODE环境变量。例如,对于要以锁模式行创建的您的会话内的 每个新表,请将IFX_DEF_TABLE_LOCKMODE设置为ROW。要覆盖此行为,请使用 CREATE TABLE或 ALTER TABLE语句并重新定义LOCK MODE子句。

单用户锁模式

如果在您的会话中创建的所有新表都要求相同的锁模式,则设置单用户锁模式。请使用 IFX_DEF_TABLE_LOCKMODE环境变量来设置单用户锁模式。例如,对于在您的会话内 创建的每一个新表都以行级别锁定来创建,请将IFX_DEF_TABLE_LOCKMODE设置为 ROW。要覆盖此行为,请使用CREATE TABLE或 ALTER TABLE语句并重新定义 LOCK MODE子句。要获取关于设置环境变量的更多信息,请参阅《GBase 8s SQL参考 指南》。

多用户锁模式

通过为同一服务器上的所有用户指定锁模式,数据库管理员可使用多用户锁模式来创建更 大的并发。然后,任何用户在那台服务器上创建的所有表都会有相同的锁模式。要启用多 用户锁模式,请在启动数据库服务器之前设置IFX_DEF_TABLE_LOCKMODE环境变量, 或设置DEF_TABLES_LOCKMODE配置参数。

优先顺序的规则

CREATE TABLE或ALTER TABLE的锁定模式有下列优先顺序的规则,按从最高优先顺 序到最低的顺序罗列:

使用 LOCK MODE 子句的 CREATE TABLE 或 ALTER TABLE SQL 语句
单用户环境变量设置
在该服务器环境中的多用户环境变量设置
配置文件中的配置参数
缺省行为(页级别锁定)
稀疏索引锁

当您将索引的锁模式从常规的更改为稀疏的锁模式时,在该索引上要求索引级别的锁,而 不是项级别锁或页级别锁,这些是常规的锁。此模式减少索引上的锁调用的数目。

当您知道不会更改索引时,请使用稀疏索引模式;即,当在该索引上执行只读操作时。

请使用常规的锁模式来使数据库服务器在必要时在索引上放置项级别锁或页级别锁。当频 繁地更新索引时,请使用此模式。

当数据库服务器执行命令来将锁模式更改为稀疏的时,在命令期间它需要在表上的排他锁。 在数据库服务器切换到稀疏锁模式之前,当前正在使用更细颗粒度的锁的任何事务都必须 完成。

智能大对象锁

CLOB或BLOB列上的锁与行上的锁是分开的。仅当访问智能大对象时,才锁定它们。 当您锁定包含CLOB或BLOB列的表时,不锁定智能大对象。如果访问是为了写,则以 更新模式锁定智能大对象,且当实际的写发生时,将该锁提升为排他的。如果访问是为了 读,则以共享的模式锁定智能大对象。数据库服务器识别事务隔离模式,因此,如果设置 Repeatable Read隔离级别,则在事务结束之前,数据库服务器不释放智能大对象读锁。

当数据库服务器检索行并更新该行指向的智能大对象时,在更新它期间,仅排他地锁定智 能大对象。

字节范围锁

您可锁定智能大对象的字节范围。字节范围锁允许事务有选择地仅锁定访问的那些字节, 因此,写用户和读用户可同时访问同一智能大对象中不同的字节范围。

要获取关于如何使用字节范围锁的信息,请参阅您的《GBase 8s性能指南》。

字节范围锁支持死锁检测。要获取关于死锁检测的信息,请参阅处理死锁。

  1. 5.3锁的持续时间

程序控制数据库锁的持续时间。当数据库关闭时,释放数据库锁。

依赖于数据库是否使用事务,表锁的持续时间有所不同。如果数据库不使用事务(即,如 果不存在事务日志且您不使用COMMIT WORK语句),则保留表锁,直到通过执行 UNLOCK TABLE语句移除它为止。

表锁、行锁和索引锁的持续时间依赖于您使用的SQL语句,并依赖于是否使用事务。

当您使用事务时,在事务结束时释放所有表锁、行锁、页锁和索引锁。当事务结束时,释 放所有锁。

  1. 5.4在修改时锁定

当数据库服务器通过更新游标访存行时,它在访存的行上放置可提升锁。如果此操作成功, 则数据库服务器知道没有其他程序可改变那一行。由于可提升锁不是排他的,其他程序可 继续读取该行。由于访存该行的程序可在它发出UPDATE或DELETE语句之前花费一些 时间,或它仅可访存下一行,因此,可提升锁可提升性能。当到了修改行时,数据库服务 器获取该行上的排他锁。如果已有可提升锁,则它将那个锁更改为排他的状态。

排他的行锁的持续时间依赖于是否在使用事务。如果未使用事务,则将修改了的行一写到 磁盘就释放该锁。当在使用事务时,保留所有这些锁,直到事务结束为止。此操作防止其 他程序使用那些可能回滚到它们的原始状态的行。

当在使用事务时,每当删除行时,就使用键锁。使用键锁防止发生下列错误:

程序A删除一行。
程序B插入有相同的键的一行。
•程序A回滚它的事务,强制数据库服务器恢复它的删除了的行。

如何处理由程序B插入的行?

通过锁定索引,数据库服务器防止第二个程序插入行,直到第一个程序提交它的事务为止。 当前的隔离级别控制在数据库服务器读取不同的行时放置的锁,如下一部分中讨论的那样。

11.6使用SELECT语句来锁定

数据库服务器放置的锁的类型和持续时间依赖于应用程序中的隔离设置,以及该SELECT 语句是否在更新游标之内。

本部分描述不同的隔离级别和更新游标。

11.6.1设置隔离级别

隔离级别是您的程序与其他程序的并发操作的隔离程度。数据库服务器提供隔离级别的选 择,反映当程序读数据时,程序如何使用锁的不同的规则集。

要设置隔离级别,请使用SET ISOLATION或SET TRANSACTION语句。SET

TRANSACTION语句还允许您设置访问模式。要获取关于访问模式的更多信息,请参阅使 用访问模式来控制数据修改。

对比 SET TRANSACTION 与 SET ISOLATION

SET TRANSACTION 语句符合 ANSI SQL-92。此语句类似于 GBase 8s SET ISOLATION 语句;然而,SET ISOLATION语句不符合ANSI,且不提供访问模式。

下表展示您使用SET TRANSACTION与SET ISOLATION语句设置的隔离级别之间的 关系。

SET TRANSACTION 相关联的

SET ISOLATION

Read Uncommitted

Dirty Read

Read Committed

Committed Read

不支持

Cursor Stability

(ANSI) Repeatable Read

(GBase 8s) Repeatable Read

Serializable

(GBase 8s) Repeatable Read

SET TRANSACTION与SET ISOLATION语句之间的主要差异是,在事务内隔离级别的 行为。对于一个事务,仅可发出SET TRANSACTION语句一次。请保证在那个事务期间 打开的任何游标都有那个隔离级别(或访问模式,如果您正在定义访问模式的话)。在启 动事务之后,您可使用SET ISOLATION语句在该事务内多次更改隔离级别。下列示例说 明使用SET ISOLATION与使用SET TRANSACTION之间的差异。

SET ISOLATION

EXEC SQL BEGIN WORK;

EXEC SQL SET ISOLATION TO DIRTY READ;

EXEC SQL SELECT …;

EXEC SQL SET ISOLATION TO REPEATABLE READ;

EXEC SQL INSERT …;

EXEC SQL COMMIT WORK;

–Executes without error

SET TRANSACTION

EXEC SQL BEGIN WORK;

EXEC SQL SET TRANSACTION ISOLATION LEVEL TO SERIALIZABLE;

EXEC SQL SELECT …;

EXEC SQL SET TRANSACTION ISOLATION LEVEL TO READ COMMITTED;

Error-876: Cannot issue SET TRANSACTION once a transaction has started.

ANSI Read Uncommitted 与 GBase 8s Dirty Read 隔离

最简单的隔离级别ANSI Read Uncommitted和GBase 8s Dirty Read实际上相当于没有隔 离。当程序访存行时,它不放置锁,且不考虑任何东西;它只是从数据库复制行,而不考 虑其他程序正在做什么。

程序总是收到完整的数据行。即使在ANSI Read Uncommitted或GBase 8s Dirty Read隔 离之下,程序也从不看到行中是否更新了某些列而有些没更新。然而,使用ANSI Read Uncommitted或GBase 8s Dirty Read隔离的程序有时会在更新程序结束它的事务之前读 取更新了的行。如果更新程序后来回滚它的事务,则读取程序处理从未真正存在的数据(在 并发问题列表中的可能性编号4。)

ANSI Read Uncommitted或GBase 8s Dirty Read是最有效率的隔离级别。读取程序从不 等待,且从不使另一程序等待。它是任何下列情况下首选的级别:

•所有表都是静态的;即,并发程序仅读取数据,且从不修改数据。

•在排他锁中保持表。

• 仅一个程序正在使用该表。

ANSI Read Committed 与 GBase 8s Committed Read 隔离

当程序请求ANSI Read Committed或GBase 8s Committed Read隔离级别时,数据库服务 器保证它从不返回未提交到数据库的行。此操作防止读取未提交的或后来回滚的数据。

ANSI Read Committed或GBase 8s Committed Read实现简单。在它访存行之前,数据库服 务器测试来确定更新进程是否在该行上放置了锁;如果没有,则它返回该行。由于已被更 新了的(但还未提交的)行在它们上面有锁,因此,此测试确保程序不读取未提交的数据。

ANSI Read Committed或 GBase 8s Committed Read实际上不在访存了的行上放置锁,因 此,此隔离级别几乎与ANSI Read Uncommitted或GBase 8s Dirty Read 一样有效率。当 将每一行数据作为独立的单元处理,未引用同一表或其他表中的其他行时,适于使用此隔 离级别。

然而,如果由于并发会话持有行上的共享锁,导致放置该测试锁的尝试不成功,则在ANSI Read Committed或GBase 8s Committed Read会话中可发生锁定冲突。要避免等待并发进 程(通过提交或回滚来)释放共享锁,GBase 8s支持Committed Read隔离级别的Last Committed选项。当此Last Committed选项生效时,由另一会话的共享锁导致该查询返回 该行的最近提交了的版本。

还可通过将USELASTCOMMITTED配置参数设置为’COMMITTED READ’或设置为 ‘ALL’,或通过当用户连接到数据库时设置sysdbopen()过程中的SET ENVIRONMENT 语句中的USELASTCOMMITTED会话环境选项,来激活Last Committed特性。要获取 关于 ANSI Read Committed 的 Last Committed 选项,或 GBase 8s Committed Read 隔离级 别的更多信息,请参阅《GBase 8s SQL指南:语法》中的SET ISOLATION语句的描述。 要获取关于USELASTCOMMITTED配置参数的信息,请参阅《GBase 8s管理员参考手 册》。

GBase 8s Cursor Stability 隔离

仅可随同 GBase 8s SQL 语句 SET ISOLATION 使用下一级别 Cursor Stabilityo

当Cursor Stability生效时,GBase 8s在访存的最新的行上放置锁。它为普通的游标放置共 享锁,或为更新游标放置可提升锁。一次仅锁定一行;即,每一次访存一行,释放前一行 上的锁(除非更新那一行,在此情况下,保持该锁直到事务结束为止。)由于Cursor Stability 一次仅锁定一行,因此,它对并发的限制低于表锁或数据库锁。

Cursor Stability确保在程序检测行时,不更改它。当程序更新基于从该行读取的数据的某 个其他的表时,这样的行稳定性非常重要。由于Cursor Stability,程序保证更新是基于当 前的信息的。它防止使用陈旧数据。

下列示例说明Cursor Stability隔离的有效使用。根据演示数据库,程序A想要为制造商 Hero (HRO)插入新的库存商品。与此同时,程序B想要删除制造商HRO以及所有与它 相关联的库存。可发生下列事件的序列:

在Cursor Stability之下操作的程序A从manufact表访存HRO行来获取制造商 代码。此操作在该行上放置共享锁。
对于那一行,程序B发出DELETE语句。由于该锁,数据库服务器让该程序等待。
程序A使用它从manufact表获得了的制造商代码在stock表中插入新行。
程序A在manufact表上关闭它的游标,或读取不同的行,释放它的锁。
程序B从等待中被释放,完成该行的删除,并继续删除那些使用制造商代码HRO 的stock的行,包括程序A刚刚插入的行。
如果程序A使用较低级别的隔离,则会发生下列序列:

程序A读取manufact表的HRO行来获取制造商代码。未放置锁。
对于那一行,程序B发出DELETE语句。操作成功。
程序B删除使用制造商代码HRO的所有stock行。
程序B结束。
程序A不知道该HRO行的它的副本现在是无效的,使用制造商代码HRO插入 新的stock行。
程序A结束。
最后,在stock中出现一行,在manufact中没有与之相匹配的制造商代码。而且,程序B 显然有问题;它未删除本应删除的行。使用Cursor Stability隔离级别可防止这些后果。

即使使用Cursor Stability,重新安排前面的场景也可能失败。只需要让程序B以与程序A 相反的序列对表操作。如果在程序B移除manufact的行之前,从stock删除它,则任何 程度的隔离都不可防止出错。每当可能发生这种错误时,所有涉及到的程序都必须使用相 同的访问顺序。

ANSI Serializable、ANSI Repeatable Read 和 GBase 8s Repeatable Read 隔离

如果需要 ANSI Serializable 或 ANSI Repeatable Read,则提供单个称为 GBase 8 s Repeatable Read 的隔离级别。这在逻辑上等同于 ANSI Serializableo 由于 ANSI Serializable 比ANSI Repeatable Read更有约束性,因此,当需要ANSI Repeatable Read时,可使用 GBase 8s Repeatable Read (虽然GBase 8s Repeatable Read在此上下文中比所需的具有更强 的约束性)。

Repeatable Read隔离级别要求数据库服务器在程序检测和访存的每行上都放置锁。对于普 通游标放置共享的,对于更新游标放置可提升的。在检测每一行时单独地放置锁。直到该 游标关闭或事务结束,才释放它们。

Repeatable Read允许使用滚动游标的程序多次读取选择了的行,并确保在读取之间不修改 或删除它们。(SQL编程描述滚动游标。)没有更低的隔离级别保证行依然存在且在第 二次读取它们时保持不变。

Repeatable Read隔离放置最多的锁且保持它们的时间最长。因此,它是降低并发最多的级 别。如果您的程序使用此隔离的级别,则请仔细考虑它放置多少锁,保持它们多长时间, 以及对其他程序可产生哪些影响。

除了对并发的影响,大量的锁还可是个问题。数据库服务器在锁定表中,按每一程序记录 锁的数目。如果超出锁的最大数目,则锁表填满,且数据库服务器不可放置锁。返回一错 误代码。管理GBase 8s数据库服务器系统的人员可检测该锁定表,并当过度使用它时通 知您。

在缺省情况下,在符合ANSI的数据库中,将隔离界别设置为Serializable。需要Serializable 隔离级别来确保根据SQL的ANSI标准执行操作。

11.6.2更新游标

更新游标是一种特殊的游标,当可能潜在地更新行时,应用程序可使用它。要使用更新游 标,请在您的应用程序中执行SELECT FOR UPDATE。更新游标使用可提升锁;即,当应 用程序访存行时,数据库服务器放置更新锁(意味着其他用户仍可查看该行),但当应用 程序使用更新游标和UPDATE…WHERE CURRENT OF更新该行时,将锁更改为排他锁。

使用更新游标的优势在于,您可查看该行,并确信在您查看它时以及您更新它之前,其他 用户不可使用更新游标来更改它或查看它。

提示:在符合ANSI的数据库中,由于任何选择游标的行为都与更新游标一样,因此更新游标 不是必需的。

11.7保留更新锁

如果用户有低于Repeatable Read的隔离级别,则一旦从游标访存下一行,数据库服务器就 释放放置在行上的更新锁。当您设置任何下列隔离级别时,使用此特性,您可使用RETAIN UPDATE LOCKS子句来保留更新锁,直到事务的结束为止:

Dirty Read
Committed Read
Cursor Stability
此特性允许您避免Repeatable Read隔离级别的开销或暂时避开诸如行上的假更新。当打 开RETAIN UPDATE LOCKS特性,且在SELECT…FOR UPDATE语句的访存期间在行上 隐式地放置更新锁时,直到事务的结束,才释放更新锁。使用RETAIN UPDATE LOCKS特 性,仅保持更新锁,直到事务的结束为止,而Repeatable Read隔离级别同时保持更新锁和 共享锁,直到会话的结束为止。

下列示例展示当您将隔离级别设置为Committed Read时,如何使用RETAIN UPDATE LOCKS子句。

SET ISOLATION TO COMMITTED READ RETAIN UPDATE LOCKS

要关闭RETAIN UPDATE LOCKS特性,请不要使用RETAIN UPDATE LOCKS子句来设置 隔离级别。当您关闭该特性时,未直接地释放更新锁。然而,从此时起,后续的访存释放 紧前的访存的更新锁,而不是更早的访存操作的更新锁。关闭的游标释放当前行上的更新 锁。

要获取关于当您指定隔离级别时,如何使用RETAIN UPDATE LOCKS特性的更多信息, 请参阅《GBase 8s SQL指南:语法》。

11.8使用某些SQL语句发生的排他锁

当您执行INSERT、UPDATE或DELETE语句时,数据库服务器使用排他锁。排他锁意 味着任何其他用户都不可更新或删除该项,直到数据库服务器移除该锁为止。此外,任何 其他用户都不可查看该行,除非他们正在使用Dirty Read隔离级别。

数据库服务器移除排他锁的时刻,依赖于该数据库是否支持事务日志记录。

要获取关于这些排他锁的更多信息,请参阅使用INSERT、UPDATE和DELETE语句加 上的锁定。

11.9锁类型的行为

GBase 8s数据库服务器在内部的锁表中存储锁。当数据库服务器读行时,它检查该行或它 的相关联的页、表或数据库是否罗列在该锁表中。如果它在锁表中,则数据库服务器还必 须检查锁类型。锁表可包含下列类型的锁。

锁名

描述

通常放置该锁的语句

S

共享锁

SELECT

X

排他锁

INSERT、 UPDATE、 DELETE

U

更新锁

在更新游标中的SELECT

B

字节锁

更新VARCHAR列的任何语句

此外,锁表可能存储意向锁。意向锁可为意向共享的(IS)、意向排他的(IX)或意向共 享排他的(SIX)。意向锁是当需要锁定较低颗粒度对象时,数据库服务器(锁管理器)放 置在较高颗粒度对象上的锁。例如,当用户以“共享的”锁定模式锁定行或页时,数据库 服务器在该表上放置IS (意向共享的)锁来提供立即的检查,检查没有其他用户持有该表 上的X锁。在此情况下,仅在该表上放置意向锁,而不是放置在行或页上。仅可以行、页 或表级别放置意向锁。

用户不对意向锁进行直接的控制;锁管理器在内部管理所有的意向锁。

下表展示如果另一用户(或数据库服务器)持有某类型的锁,则用户(或数据库服务器) 可放置哪些锁。例如,如果一个用户持有对某项的排他锁,另一用户请求任何种类的锁(排 他的、更新或共享的)都会收到错误。此外,如果用户持有对一个项的排他锁,则数据库 服务器不能在该项上放置任何意向锁。

持有X 锁

持有U 锁

持有s

持有IS

持有SIX

持有IX

请求X锁

请求U锁

请求S锁

请求IS锁

请求SIX锁

请求IX锁

要获取关于锁定如何影响性能的信息,请参阅您的《GBase 8s性能指南》。

11.10使用访问模式来控制数据修改

GBase 8s数据库服务器支持访问模式。对于事务内的行,访问模式影响读和写的并发,并 使用SET TRANSACTION语句设置访问模式。您可使用访问模式来控制共享的文件之中 的数据修改。

在缺省情况下,事务是读写的。如果您指定事务为只读,则那个事务不可执行下列任务:

插入、删除或更新表行
•创建、改变或删除任何数据库对象,诸如模式、表、临时表、索引或存储例程。

•授予或撤销权限

更新统计信息
•重命名列或表

只读访问模式禁止更新。

您可在只读事务中执行存储例程,只要该例程不试图执行任何受限制的操作。

要获取关于如何使用SET TRANSACTION语句来指定访问模式的信息,请参阅《GBase 8s SQL指南:语法》。

11.11设置锁模式

当您的程序遇到锁定了的数据时,锁模式决定会发生什么情况。当程序尝试访存或修改锁 定了的行时,会发生下列情况之一:

数据库服务器立即将SQLCODE或SQLSTATE中的错误代码返回到程序。
•数据库服务器暂挂程序,直到放置了该锁的程序移除锁为止。

•数据库服务器暂挂程序一段时间,然后,如果移除该锁,则数据库服务器将错误返 回代码发送至该程序。

使用SET LOCK MODE语句来在这些结果中选择。

  1. 11.1等待锁

当用户遇到锁时,数据库服务器的缺省行为是将错误返回到应用程序。如果您愿意无限期 地等待锁(对许多应用程序来说,这是最好的选择),则可执行下列SQL语句:

SET LOCK MODE TO WAIT

当设置此锁模式时,您的程序通常忽略其他并发程序的存在。当您的程序需要访问另一程 序已锁定了的行时,它等待,直到移除该锁位置,然后再处理。在大多数情况下,觉察不 到该延迟。

您还可等待指定的秒数,如下例所示:

SET LOCK MODE TO WAIT 20

  1. 11.2不等待锁

等待锁的缺点是等待时间可能变长(虽然正确设计的应用程序应短暂地保持它们的锁)。 当不可接受长时间延迟的可能性时,程序可执行下列语句:

SET LOCK MODE TO NOT WAIT

当程序请求锁定了的行时,它立即收到错误代码(例如,错误-107 Record is locked),并 终止当前的SQL语句。该程序必须回滚它的当前事务并重试。

当程序启动时,初始的设置为不等待。如果您正在交互地使用SQL并看到与锁定相关的 错误,则请将锁模式设置为等待。如果您正在编写程序,请考虑使其成为程序首先执行的 嵌入式SQL语句之一。

  1. 11.3等待有限的时间

您可使用下列语句要求数据库服务器设置等待的上限:

SET LOCK MODE TO WAIT 17

此语句对任何等待的长度放置17秒的上限。如果在那个时间内未移除锁,则返回错误代 码。

  1. 11.4处理死锁

死锁是一对程序阻塞彼此的进度的情况。每一程序对其他程序想要访问的一些对象有锁。 仅当所有相关的程序将它们的锁模式都设置为等待锁时,才发生死锁。

当仅涉及单个网络服务器上的数据时,GBase 8s数据库服务器立即检测到死锁。通过将错 误(错误-143 ISAM error: deadlock detected)返回给要请求锁的第二个程序,它防止发生

死锁。如果程序将它的锁模式设置为不等待锁,则程序收到该错误代码。如果即使程序将 锁模式设置为等待之后,它还收到与锁相关的错误代码,则您知道是由于即将发生的死锁。

  1. 11.5处理外部的死锁

在不同数据库服务器上的程序之间也可发生死锁。在此情况下,数据库服务器不可立即检 测到死锁。(完善的死锁检测需要在网络中的所有数据库服务器之间大量的通信。)而是 每一数据库服务器对于程序可等待的时间量设置一个上限,来在不同的数据库服务器上获 得对数据的锁。如果到时间了,则数据库服务器假定发生死锁并返回一个与锁相关的错误 代码。

也就是说,当涉及外部的数据库时,每个程序都运行最大锁等待时间。DBA可为数据库服 务器设置或修改该最大值。

11.12简单的并发

如果您不确定如何在锁定与并发之间做出选择,则可使用下列指导方针:如果您的应用程 序访问非静态表,且不存在死锁的风险,则当您成程序启动时(就在第一个CONNECT或 DATABASE语句之后),让它执行下列语句:

SET LOCK MODE TO WAIT

SET ISOLATION TO REPEATABLE READ

请忽略来自两个语句的返回代码。请执行,就如同没有其他程序存在一样。如果未出现性 能问题,则您不需再阅读这部分。

11.13保持游标

当使用事务日志记录时,GBase 8s保证在事务结束时,可回滚在事务内所作的一切。要可 靠地处理事务,数据库服务器通常应用下列规则:

•当事务结束时,关闭所有游标。

•当事务结束时,释放所有锁。

对于支持事务的大多数数据库系统,用于可靠地处理事务的规则都是正常的。然而,存在 一些情况,随同游标使用标准事务是不可能的。例如,在没有事务的情况下,下列代码正 常工作。然而,当添加事务时,关闭游标与同时使用两个游标发生冲突。

EXEC SQL DECLARE master CURSOR FOR

EXEC SQL DECLARE detail CURSOR FOR FOR UPDATE

EXEC SQL OPEN master;

while(SQLCODE == 0)

(

EXEC SQL FETCH master INTO

if(SQLCODE == 0)

(

EXEC SQL BEGIN WORK;

EXEC SQL OPEN detail USING

EXEC SQL FETCH detail

EXEC SQL UPDATE WHERE CURRENT OF detail

EXEC SQL COMMIT WORK;

}

}

EXEC SQL CLOSE master;

在此设计中,使用一个游标来扫描表。选择了的记录用作更新不同的表的基础。问题在于, 当将每一更新当做分开的事务处理时(如前一示例中伪代码所示),跟在UPDATE之后 的COMMIT WORK语句关闭所有游标,包括主游标。

最简单的替代方案是将COMMIT WORK语句和BEGIN WORK语句分别移到最后一个 语句和第一个语句,这样,对整个主表的扫描就是一个大事务。将主表的扫描作为一个大 事务来处理,有时是可能的,但如果需要更新许多行,它可变得不现实。锁的数目可能太 大,并且在程序期间持有它们。

GBase 8s数据库服务器支持的解决方案是将关键字WITH HOLD添加到主游标的声明。引 用这样的游标作为持有游标,在不在事务结束时关闭。数据库服务器仍然关闭所有其他游 标,且它仍然释放所有锁,但持有游标保持打开,直到显式地关闭它为止。

在您尝试使用持有游标之前,您必须确保了解此处描述的锁定机制,且您还必须了解正在 并发地运行的程序。每当执行COMMIT WORK时,释放所有锁,包括放置在任何通过该 持有游标访存的行上的任何锁。

对于对表的单向扫描,如果如您所愿地使用游标,则锁的移除无关紧要。然而,您可为任 何游标指定WITH HOL D,包括更新游标和滚动游标。在您这么做之前,您必须了解该事 实的含义,即,在事务结束时,释放所有锁(包括对整个表的锁)。

11.14 SQL语句高速缓存

SQL语句高速缓存是一个特性,允许您将反复地执行的同一 SQL语句存储在缓冲区中, 以便在不同的用户中可重用这些语句,而不需要每个会话都分配内存。对于包含大量准备 好的语句的应用程序,语句高速缓存可显著地提高性能。然而,当使用语句高速缓存来高 速缓存那些一次准备多次执行的语句时,性能的提升就不太显著。

当为数据库服务器启用语句高速缓存时,请使用SQL为个别的数据库服务器打开或关闭 语句高速缓存。下列语句展示如何使用SQL为当前的数据库会话打开高速缓存:

SET STATEMENT CACHE ON

下列语句展示如何使用 SQL 为当前的数据库会话关闭高速缓存:

SET STATEMENT CACHE OFF

当禁用高速缓存时,如果您尝试关闭或打开语句高速缓存,则数据库服务器返回错误。 要了解关于SET STATEMENT CACHE语句的语法的信息,请参阅《GBase 8s SQL指南:

语法》。要了解关于STMT_CACHE和STMT_CACHE_SIZE配置参数的信息,请参阅

《GBase 8s管理员参考手册》和您的《GBase 8s性能指南》。要获取关于STMT_CACHE环 境变量的信息,请参阅《GBase 8s SQL参考指南》。

11.15总结

每当多个程序并发地访问一个数据库(且当其中至少有一个可修改数据时,)所有程序必 须允许在它们读数据时,另一程序可更改该数据的可能性。数据库服务器提供锁和隔离级 别的机制,其通常允许程序运行,如同它们独占数据一样。

SET STATEMENT CACHE语句允许您将反复地使用的相同的SQL语句存储在缓冲区 中。当打开语句高速缓存时,数据库服务器存储相同的语句,因此可在不同的用户会话之 中重用它们,而无需为每个会话都分配内存。
————————————————
版权声明:本文为CSDN博主「aisirea」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/aisirea/article/details/124093002

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值