0

我们将 Ticket 对象队列持久化到数据库中。我们包含队列的实体如下所示:

@Entity
public class StoreQueueCollection {

    @Id
    private int storeId;

    @ManyToMany(fetch=FetchType.LAZY)
    @OrderColumn
    private List<Ticket> mainQueue = new ArrayList<Ticket>();

    @ManyToMany(fetch=FetchType.LAZY)
    @OrderColumn
    private List<Ticket> cancelledQueue = new ArrayList<Ticket>();

    .. etc

我们有一个操作将票从一个队列移动到另一个队列(称为 changeStatus),另一个操作将新票添加到队列末尾(称为 newTicket)。

当这两个操作在同一个队列上交错时,这些操作基本上可以工作,但我们最终会在我们的队列中产生一个“间隙”。在数据库中,这看起来像表的 order 列中缺少索引,如下所示:0、1、2、4。当队列重新加载到 Java 中时,缺少的索引成为队列中的空元素。

我们在 StoreQueueCollection 对象上使用悲观锁定来防止不一致的交错更新,但它并没有像我们预期的那样工作。通过额外的日志记录,我们会看到如下奇怪的序列:

- changeTicketStatus() 开始
- 使用 entityManager.refresh() 锁定队列
- 票 X 从队列 A 前面移除
- 队列是 entityManager.flush()ed

* newTicket() 开始
* 创建一个新票 Y,使用 entityManager.refresh() 锁定 StoreQueueCollection;
* 票 Y 添加到队列 A 的末尾
* 票 Y 的字段已初始化并且是 save()d
* 调用 refresh(),方法被阻塞

- changeTicketStatus() 恢复
    (打印内存队列显示票 X 不在队列 A 中)
- 票 X 添加到另一个队列 B
- 票 X 修改了一些字段
- 票据 X 使用 repository.save() 保存
    (打印内存队列显示票 X 不在队列 A 中)
- changeTicketStatus() 完成

* newTicket() 恢复
* 刷新()返回
    (打印内存队列 A 显示票 X 仍在队列中!)
* 票 Y 被添加到队列 A 的末尾
    (打印内存队列 A 显示票 X 和 Y 在队列中)
* 队列是 save()d

所有锁都是 LockModeType.PESSIMISTIC_WRITE,范围是 PessimisticLockScope.EXTENDED。

在这一系列执行之后,另一个线程触发一个断言,该线程检查队列中的空条目。神秘的是,队列基本是正确的(X被删除,Y被添加到最后),但是在Y之前的order列有一个空隙。

非常感谢任何关于我们做错了什么的建议!

4

1 回答 1

0

您是锁定单行,还是所有线程都锁定一个共同的“队列”行?如果是后者,请记住,您需要在希望持有锁的整个时间内维护事务。如果是前者,锁定单个(不同)行不会阻止交错操作。

您没有提及您正在使用什么后端,但有时数据库日志记录工具(如 SQL Server 的分析器和活动监视器)可以帮助确定正在发生的事情,因为您可以获得发布到的所有 SQL 语句的有序列表数据库。

于 2014-11-12T03:14:20.360 回答