Click here to view and discuss this page in DocCommentXchange. In the future, you will be sent there automatically.

SAP Sybase SQL Anywhere 16.0 (中文) » SQL Anywhere 服务器 - 编程 » 使用 SQL 进行应用程序开发 » SQL Anywhere 游标 » 游标敏感性和性能

 

更新丢失

使用可更新的游标时,防止更新丢失很重要。更新丢失是这样一种情况:两个或多个事务更新同一行,但这些事务彼此之间都不知道其它事务进行的修改,因此第二个更改会覆盖第一个修改。下面示例说明了此问题:

  1. 某个应用程序在以下针对示例数据库的查询中打开一个游标。

    SELECT ID, Quantity
    FROM Products;
    ID Quantity
    300 28
    301 54
    302 75
    ... ...
  2. 应用程序通过游标读取 ID = 300 的行。

  3. 另外一个事务使用下面的语句更新该行:

    UPDATE Products
    SET Quantity = Quantity - 10
    WHERE ID = 300;
  4. 然后,应用程序通过游标将该行值更新为 (Quantity - 5) 的值。

  5. 该行的正确的最终值是 13。如果游标预读了该行,那么该行的新值是 23。另外一个事务的更新会丢失。

在数据库应用程序中,如果预先不对行值进行验证就进行更改,那么在任何一个隔离级别都有可能丢失更新。在较高隔离级别(2 和 3),可以使用锁(读取、意图和写入锁)来确保其它事务不能对应用程序已读取的行进行更改。但在隔离级别 0 和 1,更新丢失的可能性会更大:在隔离级别 0,不能获得读取锁来防止随后的数据更改;在隔离级别 1,只能锁定当前行。使用快照隔离时不会发生更新丢失,因为任何更改旧值的尝试都会导致更新冲突。同样,在隔离级别 1 使用预取也有可能导致丢失更新,因为在客户端的预取缓冲区中应用程序定位的结果集行可能与游标中服务器定位的当前行不同。

在隔离级别 1 使用游标时,为防止丢失更新,数据库服务器支持三种不同的并发控制机制,这三个机制可由应用程序指定:

  1. 读取游标中的每一行时在该行上获得意图行锁。意图锁防止其它事务在同一行上获得意图锁或写入锁,从而防止并发更新。但是,意图锁不防碍读取行锁,因此意图锁不影响并发只读语句。

  2. 使用对值敏感的游标。对值敏感的游标可用于跟踪基础行发生更改或删除的时间,以便应用程序可以采取相应措施。

  3. 使用 FETCH FOR UPDATE,此方法可在特定行上获得意图行锁。

指定这些替代方法的方式取决于应用程序所使用的接口。对于适用于 SELECT 语句的前两个替代方法:

  • 在 ODBC 中不会发生更新丢失,因为在声明可更新游标时应用程序必须为 SQLSetStmtAttr 函数指定游标并发参数。此参数是 SQL_CONCUR_LOCK、SQL_CONCUR_VALUES、SQL_CONCUR_READ_ONLY 或 SQL_CONCUR_TIMESTAMP 之一。对于 SQL_CONCUR_LOCK,数据库服务器可获取行意图锁。对于 SQL_CONCUR_VALUES 和 SQL_CONCUR_TIMESTAMP,可使用对值敏感的游标。SQL_CONCUR_READ_ONLY 用于只读游标,是缺省值。

  • 在 JDBC 中语句的并发设置类似于 ODBC 中的设置。SQL Anywhere JDBC 驱动程序支持 JDBC 并发值 RESULTSET_CONCUR_READ_ONLY 和 RESULTSET_CONCUR_UPDATABLE。第一个值对应于 ODBC 并发设置 SQL_CONCUR_READ_ONLY,指定只读语句。第二个值对应于 ODBC SQL_CONCUR_LOCK 设置,因此使用行意图锁来防止更新丢失。无法在 JDBC 4.0 规范中直接指定对值敏感的游标。

  • 在 jConnect 中,在 API 级别支持可更新游标,但底层实现(使用 TDS)不支持通过游标进行更新。jConnect 将单独的 UPDATE 语句发送到数据库服务器来更新特定行。为避免更新丢失,应用程序必须在隔离级别为 2 或更高的情况下运行。应用程序还可以从游标发出单独的 UPDATE 语句,但必须确保 UPDATE 语句通过在其 WHERE 子句中设置相应的条件来验证自读取该行以后该行值未变化。

  • 在嵌入式 SQL 中,可通过在 SELECT 语句本身或游标声明中包括语法来设置并发说明。在 SELECT 语句中,语法 SELECT...FOR UPDATE BY LOCK 导致数据库服务器在结果集上获取意图行锁。

    或者,SELECT...FOR UPDATE BY [ VALUES | TIMESTAMP ] 促使数据库服务器将游标类型更改为对值敏感的游标,这样如果自上次读取特定行以后通过游标对该行进行了更改,在使用 FETCH 语句时应用程序会收到警告 (SQLE_ROW_UPDATED_WARNING),在使用 UPDATE WHERE CURRENT OF 语句时应用程序会收到错误 (SQLE_ROW_UPDATED_SINCE_READ)。如果删除行,应用程序也会收到错误 (SQLE_NO_CURRENT_ROW)。

嵌入式 SQL 和 ODBC 接口也支持 FETCH FOR UPDATE 功能,虽然细节方面因所使用的 API 而不同。

在嵌入式 SQL 中,应用程序使用 FETCH FOR UPDATE,而非 FETCH,促使在该行上获取意图锁。在 ODBC 中,应用程序使用 API 调用 SQLSetPos,并使用操作参数 SQL_POSITION 或 SQL_REFRESH 和锁类型参数 SQL_LOCK_EXCLUSIVE,以在行上获取意图锁。在 SQL Anywhere 中,这些锁是长期锁,会一直保持到提交或回退事务。