使用 Java 的 BufferedInputStream 将大文件存储到 MySQL 数据库时出现 java.lang.outOfMemoryError
我目前正在尝试使用 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_packet”大小,这样我就可以将最多 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您不想增加 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
为了解决这个问题,请尝试增大 JVM 堆大小。
永久增加 Java 堆大小?
http://javahowto.blogspot。 com/2006/06/6-common-errors-in-setting-java-heap.html
Just for the heck of it, try upping your JVM heap size.
increase the java heap size permanently?
http://javahowto.blogspot.com/2006/06/6-common-errors-in-setting-java-heap.html
运行 Java 代码时增加 JVM 堆大小:
upping JVM heap size when running your java code:
看起来更像是 MySQL JDBC 问题。当然,您可以考虑 GZip + 管道 I/O。
我还发现了一个糟糕的解决方案,分部分进行插入:
我们可以得出结论,对于大文件,最好将其存储在磁盘上。
尽管如此:
我认为默认缓冲区大小为 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:
We may conclude, that for large files, it is better to store it on disk.
Nevertheless:
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.