使用 Java 的 BufferedInputStream 将大文件存储到 MySQL 数据库时出现 java.lang.outOfMemoryError

发布于 2024-12-28 11:13:06 字数 2795 浏览 5 评论 0原文

我目前正在尝试使用 java 在 MySQL 5.5 数据库上存储大文件。我的主类称为 FileDatabaseTest。它具有以下方法:

import java.sql.*;
import java.io.*;

...

public class FileDatabaseTest {

...

private void uploadToDatabase(File file, String description) {
        try {
            PreparedStatement stmt = connection.prepareStatement(
                "INSERT INTO FILES (FILENAME, FILESIZE, FILEDESCRIPTION, FILEDATA) " +
                    "VALUES (?, ?, ?, ?)");
            stmt.setString(1, file.getName());
            stmt.setLong(2, file.length());
            stmt.setString(3, description);
            stmt.setBinaryStream(4, new FileInputStream(file));
            stmt.executeUpdate();
            updateFileList();
            stmt.close();
        } catch(SQLException e) {
            e.printStackTrace();
        } catch(FileNotFoundException e) {//thrown by FileInputStream constructor
            e.printStackTrace();
        } catch(SecurityException e) { //thrown by FileInputStream constructor
            e.printStackTrace();
        }
    }

...

}

数据库只有一个表 - “FILES”表,并且它具有以下列。

ID - AUTOINCREMENT, PRIMARY KEY

FILENAME - VARCHAR(100)

FILESIZE - BIGINT

FILEDESCRIPTION - VARCHAR(500)

FILEDATA - LONGBLOB

当上传小文件时,程序运行良好,但是当我上传20MB这样的文件时,上传过程非常慢。因此,我尝试将 FileInputStream 放入以下代码中的 BufferedInputStream 中:

stmt.setBinaryStream(4, new BufferedInputStream(new FileInputStream(file));

上传过程变得非常快。就像将文件复制到另一个目录一样。但是当我尝试上传超过 400mb 的文件时,出现以下错误:

Exception in thread "Thread-5" java.lang.OutOfMemoryError: Java heap space
    at com.mysql.jdbc.Buffer.ensureCapacity(Buffer.java:156)
    at com.mysql.jdbc.Buffer.writeBytesNoNull(Buffer.java:514)
    at com.mysql.jdbc.PreparedStatement.escapeblockFast(PreparedStatement.java:1169)
    at com.mysql.jdbc.PreparedStatement.streamToBytes(PreparedStatement.java:5064)
    at com.mysql.jdbc.PreparedStatement.fillSendPacket(PreparedStatement.java:2560)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2401)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2345)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2330)
    at FileDatabaseTest$2.run(FileDatabaseTest.java:312)
    at java.lang.Thread.run(Thread.java:662)

因此我尝试使用嵌入式 Apache-Derby 数据库而不是 MySQL,但没有收到错误。我能够使用 BufferedInputStream 在 Derby 数据库中上传 500MB 到 1.5G 的文件。我还观察到,当使用 BufferedInputStream 与 MySQL 服务器上传大文件时,JVM 消耗了大量内存,而当我在 Derby 数据库中使用它时,JVM 的内存使用量保持在 85MB 到 100MB 左右。

我对 MySQL 比较陌生,我只是使用它的默认配置。我在其配置中唯一更改的是“max_allowed_pa​​cket”大小,这样我就可以将最多 2GB 的文件上传到数据库。所以我想知道错误是从哪里来的。是 MySQL 还是 MySQL Connector/J 的 bug?或者我的代码有问题吗?

我在这里试图实现的是能够使用java上传大文件(最大2GB)到MySQL服务器,而不增加java堆空间。

Im currently experimenting on storing large files on a MySQL 5.5 database using java. My main class is called FileDatabaseTest. It has the following method:

import java.sql.*;
import java.io.*;

...

public class FileDatabaseTest {

...

private void uploadToDatabase(File file, String description) {
        try {
            PreparedStatement stmt = connection.prepareStatement(
                "INSERT INTO FILES (FILENAME, FILESIZE, FILEDESCRIPTION, FILEDATA) " +
                    "VALUES (?, ?, ?, ?)");
            stmt.setString(1, file.getName());
            stmt.setLong(2, file.length());
            stmt.setString(3, description);
            stmt.setBinaryStream(4, new FileInputStream(file));
            stmt.executeUpdate();
            updateFileList();
            stmt.close();
        } catch(SQLException e) {
            e.printStackTrace();
        } catch(FileNotFoundException e) {//thrown by FileInputStream constructor
            e.printStackTrace();
        } catch(SecurityException e) { //thrown by FileInputStream constructor
            e.printStackTrace();
        }
    }

...

}

The database has only one Table - the "FILES" table, and it has the following columns.

ID - AUTOINCREMENT, PRIMARY KEY

FILENAME - VARCHAR(100)

FILESIZE - BIGINT

FILEDESCRIPTION - VARCHAR(500)

FILEDATA - LONGBLOB

The program is working fine when uploading small documents, but when I upload files like 20MB, the upload process is very slow. So I tried putting the FileInputStream inside a BufferedInputStream in the following code:

stmt.setBinaryStream(4, new BufferedInputStream(new FileInputStream(file));

The upload process became very fast. Its like just copying the file to another directory. But when I tried to upload files more than 400mb, I got the following error:

Exception in thread "Thread-5" java.lang.OutOfMemoryError: Java heap space
    at com.mysql.jdbc.Buffer.ensureCapacity(Buffer.java:156)
    at com.mysql.jdbc.Buffer.writeBytesNoNull(Buffer.java:514)
    at com.mysql.jdbc.PreparedStatement.escapeblockFast(PreparedStatement.java:1169)
    at com.mysql.jdbc.PreparedStatement.streamToBytes(PreparedStatement.java:5064)
    at com.mysql.jdbc.PreparedStatement.fillSendPacket(PreparedStatement.java:2560)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2401)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2345)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2330)
    at FileDatabaseTest$2.run(FileDatabaseTest.java:312)
    at java.lang.Thread.run(Thread.java:662)

So I tried using an embedded Apache-Derby database instead of MySQL, and I didn't get the error. I was able to upload 500MB to 1.5G files in the Derby database using the BufferedInputStream. I also observed that when using the BufferedInputStream with the MySQL server in uploading large files, the JVM is eating a lot of memory, while when I used it in the Derby database, the JVM's memory usage is maintaned at around 85MB TO 100MB.

I am relatively new to MySQL and I am just using its default configurations. The only thing I changed in its configuration is the "max_allowed_packet" size so I can upload up to 2GB file to the database. So I wonder where the error came from. Is it a bug of MySQL or the MySQL connector/J? or is there something wrong with my code?

What I am trying to achieve here is to be able to upload large files (up to 2GB) to the MySQL server using java, without increasing the java heap space.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

日裸衫吸 2025-01-04 11:13:06

如果您不想增加 JVM 堆大小,还有另一种解决方法:

首先,您的 MySQL 版本应该高于 5.0。

其次,Statement.getResultSetType() 应该是 TYPE_FORWARD_ONLY,ResultSetConcurrency 应该是 CONCUR_READ_ONLY(默认)。

第三,包括以下行之一:
1).statement.setFetchSize(Integer.MIN_VALUE);
2).((com.mysql.jdbc.Statement)stat).enableStreamingResults();

现在您将一一获取结果行

There are another resolve method, if you don't want to upping your JVM heap size:

First, your MySQL version should newer than 5.0.

Second, Statement.getResultSetType() should be TYPE_FORWARD_ONLY and ResultSetConcurrency should be CONCUR_READ_ONLY(default).

Third, include ONE of these lines:
1).statement.setFetchSize(Integer.MIN_VALUE);
2).((com.mysql.jdbc.Statement)stat).enableStreamingResults();

now you will fetch result rows one by one

花桑 2025-01-04 11:13:06

运行 Java 代码时增加 JVM 堆大小:

right click your java file
    ->run as->run configurations->arguments->VM arguments

upping JVM heap size when running your java code:

right click your java file
    ->run as->run configurations->arguments->VM arguments
时常饿 2025-01-04 11:13:06

看起来更像是 MySQL JDBC 问题。当然,您可以考虑 GZip + 管道 I/O。

我还发现了一个糟糕的解决方案,分部分进行插入:

UPDATE FILES SET FILEDATA = CONCAT(FILEDATA, ?)

我们可以得出结论,对于大文件,最好将其存储在磁盘上。

尽管如此:

final int SIZE = 1024*128;
InputStream in = new BufferedInputStream(new FileInputStream(file), SIZE);
stmt.setBinaryStream(4, in);
stmt.executeUpdate();
updateFileList();
stmt.close();
in.close(); //?

我认为默认缓冲区大小为 8 KB,较大的缓冲区可能会显示不同的内存行为,也许可以阐明问题。

尝试封闭自己应该不会有什么坏处。

It seems more to be a MySQL JDBC problem. Of course you migth consider a GZip + Piped I/O.

I also found a terrible solution, doing the insert in parts:

UPDATE FILES SET FILEDATA = CONCAT(FILEDATA, ?)

We may conclude, that for large files, it is better to store it on disk.

Nevertheless:

final int SIZE = 1024*128;
InputStream in = new BufferedInputStream(new FileInputStream(file), SIZE);
stmt.setBinaryStream(4, in);
stmt.executeUpdate();
updateFileList();
stmt.close();
in.close(); //?

The default buffer size is 8 KB I think, a larger buffer might show a different memory behaviour, maybe shedding some light on the problem.

Closing oneself should not hurt to try.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文