为什么即使数据库已清空,HSQLDB 在多次插入后仍会产生 OutOfMemoryError?
Why does HSQLDB produce an OutOfMemoryError after many inserts even though the database is emptied?
我们正在使用 HSQLDB 进行一系列 JUnit 集成测试。对于一个特定的测试,我需要加载一堆数据来验证我们 运行 在数据库上的一些算法。套件中的每个测试都会插入一个大批量,测试算法然后删除所有记录。不幸的是,HSQLDB 最终会抛出 OutOfMemoryError,即使每次都清除了所有记录并且数据库中的最大记录数在任何给定时间都没有改变。
这里有一个极简主义的 JUnit 测试来重现这一点。如您所见,它只是插入然后删除了一堆行。在导致错误的删除之后,HSQLDB 在内存中保留了什么?我可以更改什么才能 运行 无限期地插入-删除(或者至少有足够的时间执行所有测试)?
package mypackage;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Verifying HSQLDB OutOfMemoryError
*
* @author Ricardo van den Broek
*/
public class HsqlDbTest {
@Test
public void test() throws Exception {
executeSql("CREATE TABLE MY_TABLE (MY_COLUMN CLOB)");
String str = "TESTTESTTESTTESTTESTTESTTESTTEST";
String x = String.format("INSERT INTO MY_TABLE VALUES('%S')", str);
for (int i=0;i<1000;i++){
System.out.println("Starting batch: "+i);
for (int j=0; j<10000; j++) {
executeSql(x);
}
System.out.println("Inserted: "+getCount());
executeSql("DELETE FROM MY_TABLE");
System.out.println("After delete: "+getCount());
}
}
private void executeSql(String sql) throws SQLException {
Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
PreparedStatement ps = c.prepareStatement(sql);
ps.executeUpdate();
ps.close();
c.close();
}
private long getCount() throws Exception {
Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
PreparedStatement ps = c.prepareStatement("SELECT COUNT(*) FROM MY_TABLE");
ResultSet resultSet = ps.executeQuery();
if (resultSet.next()) {
long count = resultSet.getLong(1);
ps.close();
c.close();
return count;
}
throw new Exception("Should not happen");
}
}
输出:
Starting batch: 0
Inserted: 10000
After delete: 0
Starting batch: 1
# ...
# Omitting some repetition
# ...
Inserted: 10000
After delete: 0
Starting batch: 10
java.sql.SQLException: java.lang.OutOfMemoryError: Java heap space
注意:我知道我可以增加单元测试的内存限制或在两者之间关闭数据库,但在有人可以向我提供合理的解释之前,我觉得我应该这样做能够期望清空内存中的 HSQLDB 实际上也应该释放它正在使用的内存。
2.5.1 之前的版本存在这种现象。
这不是错误。在没有文件的 mem:
(仅内存)数据库中,当列类型为 CLOB(与 VARCHAR 相对)时,String 对象存储在单独的内存 LOB 存储中,该存储在删除行时不会被清除。您需要不时执行 CHECKPOINT 以清除 LOB 存储。在每个 运行.
的末尾添加 executeSQL("CHECKPOINT")
您可以将列声明为 LONGVARCHAR 或任何具有足够大大小的 VARCHAR(n),以避免使用 CHECKPOINT 语句。
此问题不会出现在 file:
数据库中,它使用文件存储 LOB,即使 table 是内存 table。在这些数据库中,CHECKPOINT 释放 .lobs
文件中已删除块占用的空间。这些空间随后会重新用于新的 LOB。
在最新版本 2.5.1 中,当从 mem:
个数据库中删除 lob 时,也会执行自动检查点。此处,hsqldb.log_size
设置适用于触发检查点的已删除 lob 数据总量(以 MB 为单位)。
我们正在使用 HSQLDB 进行一系列 JUnit 集成测试。对于一个特定的测试,我需要加载一堆数据来验证我们 运行 在数据库上的一些算法。套件中的每个测试都会插入一个大批量,测试算法然后删除所有记录。不幸的是,HSQLDB 最终会抛出 OutOfMemoryError,即使每次都清除了所有记录并且数据库中的最大记录数在任何给定时间都没有改变。
这里有一个极简主义的 JUnit 测试来重现这一点。如您所见,它只是插入然后删除了一堆行。在导致错误的删除之后,HSQLDB 在内存中保留了什么?我可以更改什么才能 运行 无限期地插入-删除(或者至少有足够的时间执行所有测试)?
package mypackage;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Verifying HSQLDB OutOfMemoryError
*
* @author Ricardo van den Broek
*/
public class HsqlDbTest {
@Test
public void test() throws Exception {
executeSql("CREATE TABLE MY_TABLE (MY_COLUMN CLOB)");
String str = "TESTTESTTESTTESTTESTTESTTESTTEST";
String x = String.format("INSERT INTO MY_TABLE VALUES('%S')", str);
for (int i=0;i<1000;i++){
System.out.println("Starting batch: "+i);
for (int j=0; j<10000; j++) {
executeSql(x);
}
System.out.println("Inserted: "+getCount());
executeSql("DELETE FROM MY_TABLE");
System.out.println("After delete: "+getCount());
}
}
private void executeSql(String sql) throws SQLException {
Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
PreparedStatement ps = c.prepareStatement(sql);
ps.executeUpdate();
ps.close();
c.close();
}
private long getCount() throws Exception {
Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
PreparedStatement ps = c.prepareStatement("SELECT COUNT(*) FROM MY_TABLE");
ResultSet resultSet = ps.executeQuery();
if (resultSet.next()) {
long count = resultSet.getLong(1);
ps.close();
c.close();
return count;
}
throw new Exception("Should not happen");
}
}
输出:
Starting batch: 0
Inserted: 10000
After delete: 0
Starting batch: 1
# ...
# Omitting some repetition
# ...
Inserted: 10000
After delete: 0
Starting batch: 10
java.sql.SQLException: java.lang.OutOfMemoryError: Java heap space
注意:我知道我可以增加单元测试的内存限制或在两者之间关闭数据库,但在有人可以向我提供合理的解释之前,我觉得我应该这样做能够期望清空内存中的 HSQLDB 实际上也应该释放它正在使用的内存。
2.5.1 之前的版本存在这种现象。
这不是错误。在没有文件的 mem:
(仅内存)数据库中,当列类型为 CLOB(与 VARCHAR 相对)时,String 对象存储在单独的内存 LOB 存储中,该存储在删除行时不会被清除。您需要不时执行 CHECKPOINT 以清除 LOB 存储。在每个 运行.
executeSQL("CHECKPOINT")
您可以将列声明为 LONGVARCHAR 或任何具有足够大大小的 VARCHAR(n),以避免使用 CHECKPOINT 语句。
此问题不会出现在 file:
数据库中,它使用文件存储 LOB,即使 table 是内存 table。在这些数据库中,CHECKPOINT 释放 .lobs
文件中已删除块占用的空间。这些空间随后会重新用于新的 LOB。
在最新版本 2.5.1 中,当从 mem:
个数据库中删除 lob 时,也会执行自动检查点。此处,hsqldb.log_size
设置适用于触发检查点的已删除 lob 数据总量(以 MB 为单位)。