【问题标题】:Concurrent use of same JDBC connection by multiple threads多线程并发使用同一个JDBC连接
【发布时间】:2015-12-31 20:31:12
【问题描述】:

我试图更好地理解如果多个线程尝试同时使用相同的 JDBC 连接执行不同的 sql 查询会发生什么。

  • 结果在功能上是否正确?

  • 性能影响是什么?

  • 线程A 是否必须等待线程B 完全完成其查询?

  • 或者线程A 是否能够在线程B 发送查询后立即发送查询,之后数据库将并行执行这两个查询?


我看到 Apache DBCP 使用同步协议来确保从池中获得的连接从池中删除,并使其不可用,直到它们被关闭。这似乎比它需要的更不方便。我正在考虑通过创建打开连接的静态列表并以循环方式分发它们来构建我自己的“池”。

我不介意偶尔的性能下降,每次使用后不必关闭连接的便利似乎非常吸引人。我这样做有什么缺点吗?

【问题讨论】:

  • 连接池不仅仅是一组连接。您应该真正了解连接池提供了什么这里的参数可以提供一些提示commons.apache.org/proper/commons-dbcp/configuration.html
  • 我浏览了界面上的配置和其他 API,但没有一个对我的用例感兴趣。因此,为什么我最终编写了自己的实现,它大约有 10 行代码,完全符合我的需要,最大限度地减少同步开销,并且省去了在百万个地方添加 connection.close() 的麻烦。
  • 在单个连接上多路复用多个线程是一个非常糟糕的主意。只需使用连接池,检查连接并在完成后将其返回池(关闭它)。例如,您是否考虑过如果一个线程想要提交而另一个线程想要回滚会发生什么?您要么丢失数据,要么提交了错误的数据。那是一个真正糟糕的地方。另外,但不使用Connection.close(),你会让别人很不清楚你在做什么。
  • 尝试使用 PostgreSQL 42.2.5 JDBC 驱动程序执行此操作,但两个线程都被阻塞了。

标签: java jdbc database-connection connection-pooling apache-commons-dbcp


【解决方案1】:

我使用 AWS RDS Postgres 数据库和 Java 11 运行了以下一组测试:

  1. 创建一个包含 11M 行的表,每行包含一个 TEXT 列,填充随机的 100 个字符的字符串

  2. 随机选择一个5个字符的字符串,并在上表中搜索该字符串的部分匹配项

  3. 上述查询返回结果所需的时间。在我的情况下,大约需要 23 秒。因为返回的结果很少,我们可以得出结论,这 23 秒的大部分时间都花在等待数据库运行全表扫描,而不是发送请求/响应数据包

  4. 使用不同的连接并行运行多个查询(使用不同的关键字)。就我而言,我看到它们都在大约 23 秒内完成。即,查询正在高效地并行化

  5. 使用相同的连接在并行线程上运行多个查询。我现在看到第一个结果在大约 23 秒内返回。第二个结果在约 46 秒内返回。约 1 分钟内的第三次。等等。所有结果在功能上都是正确的,因为它们与该线程查询的特定关键字匹配

补充一下 Joni 之前提到的内容,他的结论也与我在 Postgres 上看到的行为相符。如果同时在同一连接上发送多个查询,似乎所有“正确性”都得以保留,但所有并行性优势都将丢失

【讨论】:

    【解决方案2】:

    由于 JDBC 规范没有保证并发执行,这个问题只能通过测试您感兴趣的驱动程序或阅读它们的源代码来回答。

    在 MySQL Connector/J 的情况下,execute 语句的所有方法都使用synchronized 块锁定连接。也就是说,如果一个线程正在运行查询,则使用该连接的其他线程将被阻塞,直到它完成。

    【讨论】:

      【解决方案3】:

      以错误的方式做事会产生不确定的结果...如果有人运行一些测试,也许他们会准确回答您的所有问题,但随后会出现一个新的 JVM,或者有人在另一个 jdbc 驱动程序或数据库版本上尝试它,或者他们遇到了一组不同的竞争条件,或者尝试了另一个平台或 JVM 实现,然后发生了另一个不同的未定义结果。

      如果两个线程同时修改相同的状态,任何事情都可能发生,具体取决于时间。也许第二个会覆盖第一个查询,然后都运行相同的查询。也许库会检测到您的错误并抛出异常。我不知道也不会打扰测试......(或者也许有人已经知道或者应该很明显会发生什么)所以这不是“答案”,而只是一些建议。只需使用连接池,或使用同步块来确保不会发生问题。

      【讨论】:

      • 关于发出和运行查询的 JDBC 连接规范是 thread safe。我真的更关心性能影响。
      • 我假设在这种情况下线程安全意味着它的所有方法都是线程安全的。因此,不同线程的 2 次调用不会相互干扰,但许多依赖于特定顺序的调用可能会失败(例如 rollback())。
      • 如果您使用 Connection 中的全部功能,我明白您的观点,即存在极端情况。然而,我不是。事实上,我已经围绕 Connection 设置了一个包装器,它只允许执行查询,仅此而已。我更关心在同一连接上执行多个查询对性能的影响。
      【解决方案4】:

      我们不得不禁用 Websphere 上的语句缓存,因为它在 PreparedStatement 级别抛出 ArrayOutOfBoundsException。 问题是有些人虽然与多个线程共享连接很聪明。 他说这是为了节省连接,但是多线程查询没有意义,因为数据库不会并行运行它们。

      还有一个问题是 java runnables 因为它们使用相同的连接而相互阻塞。

      所以这只是不做的事情,没有任何收获。

      websphere 中有一个选项可以检测这种多线程访问。 自从我们在开发中使用码头以来,我实现了自己的。

      【讨论】:

        猜你喜欢
        • 2014-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多