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

时间:2012-01-23 09:44:01

标签: java mysql database derby bufferedinputstream

我目前正在尝试使用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连接器/ J的错误吗?或者我的代码有问题吗?

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

4 个答案:

答案 0 :(得分:1)

如果您不想增加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();

现在您将逐个获取结果行

答案 1 :(得分:0)

答案 2 :(得分:0)

在运行java代码时提升JVM堆大小:

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

答案 3 :(得分:0)

似乎更像是一个MySQL JDBC问题。当然,您可以考虑使用GZip + Piped 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,较大的缓冲区可能会显示不同的内存行为,可能会对问题有所了解。

关闭自己不应该伤害尝试。