我们将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时,缺少的索引将成为队列中的null元素。 / p>
我们在StoreQueueCollection对象上使用悲观锁定来防止不一致的交错更新,但它没有像我们预期的那样工作。通过额外的日志记录,我们可以看到如下奇怪的序列:
- changeTicketStatus() starts - lock the queue using entityManager.refresh() - ticket X removed from front of queue A - queue is entityManager.flush()ed * newTicket() starts * creates a new ticket Y, locks the StoreQueueCollection using entityManager.refresh(); * ticket Y added to the end of queue A * ticket Y has fields initialized and is save()d * call refresh(), method is blocked - changeTicketStatus() resumes (printing in-memory queue shows that ticket X is not in queue A) - ticket X added to another queue B - ticket X has some fields modified - ticket X is saved using repository.save() (printing in-memory queue shows that ticket X is not in queue A) - changeTicketStatus() completes * newTicket() resumes * refresh() returns (printing in-memory queue A shows that ticket X is still in queue!) * ticket Y is added to end of queue A (printing in-memory queue A shows that ticket X and Y are in the queue) * queues are save()d
所有锁都是LockModeType.PESSIMISTIC_WRITE,范围是PessimisticLockScope.EXTENDED。
在执行此序列之后,断言从另一个线程触发,该线程检查队列中的空条目。神秘的是,队列基本上是正确的(X被删除,Y被添加到结尾),但是在Y之前的顺序列中有一个间隙。
非常感谢任何关于我们做错事的建议!
答案 0 :(得分:0)
您是锁定单行,还是所有线程锁定了一个常见的队列&#39;行?如果是后者,请记住,您需要在希望锁定的整个时间内维护一个事务。如果是前者,锁定单个(不同)行不会做任何事情来防止交错操作。
您没有提及您正在使用的后端,但有时数据库记录工具(如SQL Server的分析器和活动监视器)可以帮助您确定正在发生的事情,因为您可以获取发布到数据库的所有SQL语句的有序列表。