跨多线程的单一事务解决方案

据我所知,所有事务都是线程绑定的(即上下文存储在ThreadLocal中)。例如,如果:

  1. 我在事务性父方法中启动事务
  2. 在异步调用中使数据库插入 #1
  3. 在另一个异步调用中插入数据库 #2

然后,这将产生两个不同的事务(每个插入一个),即使它们共享相同的“事务”父级。

例如,假设我执行两个插入(并使用一个非常简单的示例,即不使用执行器或可简化的未来,等等):

@Transactional
public void addInTransactionWithAnnotation() {
    addNewRow();
    addNewRow();
}

将根据需要将两个插入作为同一事务的一部分执行。

但是,如果我想并行化这些插入以提高性能:

@Transactional
public void addInTransactionWithAnnotation() {
    new Thread(this::addNewRow).start();
    new Thread(this::addNewRow).start();
}

然后,这些生成的线程中的每一个都不会参与事务,因为事务是线程绑定的。

关键问题:有没有办法安全地将事务传播到子线程?

我想到的解决这个问题的唯一解决方案:

  1. 使用 JTA 或一些 XA 管理器,根据定义,它们应该能够做到这一点。但是,理想情况下,我不想在我的解决方案中使用XA,因为它的开销很大
  2. 将我想要执行的所有事务性工作(在上面的示例中为函数)管道传输到单个线程,并以多线程方式完成所有先前的工作。addNewRow()
  3. 想办法利用TransitableThreadLocal在事务状态上并将其传播到子线程。我不知道该怎么做。

有没有可能的解决方案?即使它的味道有点像解决方法(就像我上面的解决方案一样)?


答案 1

JTA API 有几个方法可以隐式地对当前线程的事务进行操作,但它不会阻止您在线程之间移动或复制事务,或者对未绑定到当前(或任何其他)线程的事务执行某些操作。这会导致头痛的无休止,但这不是最糟糕的部分......

对于原始的 JDBC,您根本没有 JTA 事务。你有一个JDBC连接,它对交易上下文有自己的想法。在这种情况下,事务是连接绑定的,而不是线程绑定的。传递连接,tx随之而去。但是连接不一定是线程安全的,并且无论如何都可能是性能瓶颈,因此在多个并发线程之间共享一个连接并不能真正帮助您。您可能需要多个连接,这些连接认为它们位于同一事务中,这意味着您需要XA,因为这是数据库识别此类情况的方式。此时,您将返回到 JTA,但现在图片中有一个 JCA 来正确处理连接管理。简而言之,您已经重新发明了 JavaEE 应用程序服务器。

对于在JDBC上分层的框架,例如像Hibernate这样的ORM,你有一个额外的复杂性:它们的抽象不一定是线程安全的。因此,您不能拥有同时绑定到多个线程的会话。但是,您可以有多个并发会话,每个会话都参与同一个 XA 事务。

像往常一样,它归结为阿姆达尔定律。如果使用每个 tx 多个连接以允许多个并发线程共享数据库 I/O 工作所获得的加速相对于您从批处理中获得的快,那么 XA 的开销是值得的。如果加速是在本地计算中,并且 db I/O 是一个小问题,那么处理 JDBC 连接并将非 IO 计算工作卸载到线程池的单个线程就是要走的路。


答案 2

首先,澄清一下:如果您想加快多个相同类型的插入物的速度,正如您的示例所建议的那样,通过在同一螺纹中发出插入件并使用某种类型的批量插入,您可能会获得最佳性能。根据您的DBMS,有几种可用的技术,请查看:

至于你的实际问题,我个人会尝试将所有工作管道传输到工作线程。这是最简单的选择,因为您不需要弄乱 s 或事务登记/除名。此外,一旦您的工作单元在同一线程中,如果您很聪明,则可以应用上述批处理技术以获得更好的性能。ThreadLocal

最后,将管道工作连接到工作线程并不意味着必须具有单个工作线程,如果它确实对您的应用程序有益,则可以拥有一个工作线程池并实现一些并行性。从生产者/消费者的角度思考。


推荐