使用 JDBC 管理与 mysql 数据库的连接

Managing Connections to mysql DB using JDBC

背景

需要使用 JDBC 处理来自 mysql 数据库的近 6000 万条记录。这里的交互只是"selection" 从DB 基于主键,id。获取的字段是 id、name 和 description。其他一些异步线程会处理抓取的数据,不涉及DB交互。

现在这个程序将一系列 id 作为参数,例如1 到 100、101 到 200 等等,这个程序的多个实例将 运行 在不同的机器上。因此每个程序实例将处理不同范围的 id。

这是创建 backfill 数据的一次性作业,但会命中生产数据库以供选择。

观察

 mysql> SHOW STATUS WHERE `variable_name` = 'Threads_connected';
 +-------------------+-------+
 | Variable_name     | Value |
 +-------------------+-------+
 | Threads_connected | 12    |
 +-------------------+-------+

当我使用 25 到 95 的范围时,它只有 20 条记录,但观察到的连接数是 12,并且在整个处理过程中它们仍然如此。

代码

public void processRange(String fromid, String toid) {
    if (!fromid.isEmpty() && !toid.isEmpty()) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            this.connection = DriverManager.getConnection(this.url, this.user, this.password);
            this.statement = connection.prepareStatement("SELECT id, name, contents FROM sometable WHERE id >= " + fromid + " and id <= "  + toid);
            this.results = statement.executeQuery();

            while(results.next()) {

              if(name != null && !name.isEmpty() && contents != null && !contents.isEmpty()) {
               //PROCESS using async futures
                }
            }
        } catch (SQLException sqle) {
            sqle.printStackTrace();
        } catch (Exception exp) {
            exp.printStackTrace();
        } finally {
            try {
                if(results != null) {
                    this.results.close();
                }
                if(statement != null) {
                    this.statement.close();
                }
                if(connection != null) {
                    this.connection.close();
                }
            } catch (SQLException sqle) {
                sqle.printStackTrace();
            }
        }
    }
}

问题

这是非常简单的 JDBC 代码。现在,如果我 运行 此过程的多个实例,如 背景 中所述,我如何确保数据库不会因 "Too many connections" 错误而关闭。有什么方法可以只获取数据,关闭连接并处理异步 "offline" 以便尽快释放连接。

另一方面,我认为循环遍历范围然后为每个 id 获取数据是解决此问题的最糟糕的方法,而不是批量获取。如果有人不这么认为,请发表评论。

更新

以现在的速度,要几个月才能完成任务...!还有其他并行方法可以使整个任务更快吗?

我也在尝试使用 MAP REDUCE 来实现相同的目的,但我不确定这是否是一个有效的用例。

以上观察是基于本地机器。当我在一个相对强大的盒子上测试它时,与最大数量相比,连接数非常少。我正在 运行 在同一台机器上查看该过程的 5 个实例,查看由结果集处理的 10000 个 ID(总共 50K)的范围。我仍然有 17 个连接,允许的最大连接数为 500。但是这样完成需要花费很多时间,CPU 使用率达到 99%。因此需要进行一些调整以使其 运行 更快或分发到其他机器。

情况一:大部分代码在MySQL,不在Java:

为此进程创建一个特殊的 "user" 登录名。设置 max_user_connections = 12。 "Catch" 尝试连接时的相关错误。稍等片刻,然后重试。

请注意,根据您使用的 MySQL 版本、您的代码的作用、您拥有的 CPU 核心数等,拥有太多线程实际上会损害性能。一些较旧的基准显示 4-8 作为实际限制。一些较新的(5.7 版)显示 64。(但这是针对特定工作负载的,而不是您的。)

完成所有这些努力后,您可能会发现拥有多个异步进程并没有您希望的那么大的帮助。

情况2:大部分处理在外部MySQL:

拥有 max_connections = 1000 并在大量服务器上生成大量线程应该没问题。这是因为每个 MySQL 线程大部分时间都处于 'Sleep' 状态。

问题不在于数据库。瓶颈是 FTP。这些图像是根据从数据库中获取的信息创建的,并且应该 FTPed 到另一台主机。因此,不是将每张图片都放在 FTP 服务器上,而是将多张图片压缩在一起,然后上传。