这是我在使用 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,这是可以重现的。所以我要删除Java和JDBC标记,因为这绝对是与服务器相关的。