0

这是我在使用 Sybase ASE 15.7 对应用程序进行压力测试时偶然发现的一件非常令人不安的事情。

我们有下表:

CREATE TABLE foo
(
    i INT NOT NULL,
    blob  IMAGE    
);
ALTER TABLE foo ADD PRIMARY KEY (i);

甚至在开始测试之前,该表在 IMAGE 列中有一行包含一些数据测试期间不会删除或插入任何行。所以表总是包含一行。列blob仅更新(在下面的事务T1中)为某个值(非 NULL)。

然后,我们有以下两个事务:

T1: UPDATE foo SET blob=<some not null value> WHERE i=1
T2: SELECT * FROM foo WHERE i=1

由于某种原因,上述事务可能在负载下死锁(大约 10 个线程在循环中执行T1 20 次,另外 10 个线程在循环中执行T2 20 次)。

这已经足够奇怪了,但还有更多。T1总是被选为死锁牺牲品。因此,应用程序逻辑在发生死锁(错误代码 1205)时只需重试T1。这应该有效,并且通常应该是故事的结尾。然而 …

…碰巧有时T2会检索其中blob列的值为 NULL 的行!即使表已经从一行开始并且更新只是将先前的(非NULL)值重置为其他一些(非NULL)值,也是如此。这在每次测试运行中都是 100% 可重现的。

这可以通过 READ COMMITTED 序列化级别观察到。

我验证了上述行为也发生在TEXT列类型而不是VARCHAR

我还验证了在事务T1中获取表foo上的排他锁可以使问题消失。

所以我想了解从根本上破坏事务隔离的东西怎么可能?事实上,我认为这比事务隔离更糟糕,因为T1从不将blob列的值设置为NULL

测试代码是使用jconn4.jar驱动程序(com.sybase.jdbc4.jdbc.SybDriver类)用 Java 编写的,所以我不排除这可能是 JDBC 驱动程序错误。

更新

只需使用isql并并行生成多个在循环中连续执行T1的 shell,这是可以重现的。所以我要删除JavaJDBC标记,因为这绝对是与服务器相关的。

4

2 回答 2

0

默认情况下,您的示例创建表代码将创建一个所有页锁定表,除非您的 DBA 已通过 sp_configure 将系统范围的“锁定方案”参数更改为另一个值(您可以通过 sp_configure“锁定方案”以任何人身份自行检查。

除非您有大量行,否则它们都将位于单个数据页上,因为 int 只有 4 个字节长,并且 blob 数据存储在表的末尾(除非您使用行内 LOB ASE15.7 及更高版本中的功能)。这就是你遇到死锁的原因。根据定义,您创建了一个热点,所有数据都在页面级别访问。在使用大于 2k 的较大页面大小时,这种情况更有可能发生,因为就其性质而言,它们每页将有更多行,并且所有页面都锁定,争用的可能性更大。

如上所述,将您的锁定方案更改为数据行(除非您计划拥有非常高的行数),您的问题应该会消失。我将补充一点,您的 blob 列看起来允许代码中的空值,因此如果您的图像列中有空值,您还应该考虑为表设置“dealloc_first_txtpg”属性以避免浪费空间。

于 2017-11-14T13:38:15.253 回答
0

我们见过各种隔离级别为 1 的奇怪东西。我的印象是,当 T2 正在进行时,T1 可以更改数据,T2 可能会返回 T1 的中间结果。

尝试隔离级别 2,看看它是否有帮助(对我们有用)。

于 2019-01-07T17:05:22.430 回答