提交 423a9972 authored 作者: Thomas Mueller's avatar Thomas Mueller

Lob in database: storing lob objects was not correctly synchronized. This was…

Lob in database: storing lob objects was not correctly synchronized. This was specially a problem when using Connection.createBlob() / createClob().
上级 b336aba3
......@@ -14,6 +14,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.h2.constant.ErrorCode;
......@@ -79,7 +80,7 @@ public class LobStorage {
/**
* Initialize the lob storage.
*/
public void init() {
public synchronized void init() {
if (init) {
return;
}
......@@ -107,14 +108,18 @@ public class LobStorage {
}
private long getNextLobId() throws SQLException {
PreparedStatement prep = prepare("SELECT MAX(LOB) FROM " + LOB_MAP);
String sql = "SELECT MAX(LOB) FROM " + LOB_MAP;
PreparedStatement prep = prepare(sql);
ResultSet rs = prep.executeQuery();
rs.next();
long x = rs.getLong(1) + 1;
prep = prepare("SELECT MAX(ID) FROM " + LOBS);
reuse(sql, prep);
sql = "SELECT MAX(ID) FROM " + LOBS;
prep = prepare(sql);
rs = prep.executeQuery();
rs.next();
x = Math.max(x, rs.getLong(1) + 1);
reuse(sql, prep);
return x;
}
......@@ -127,12 +132,14 @@ public class LobStorage {
if (SysProperties.LOB_IN_DATABASE) {
init();
try {
PreparedStatement prep = prepare("SELECT ID FROM " + LOBS + " WHERE TABLE = ?");
String sql = "SELECT ID FROM " + LOBS + " WHERE TABLE = ?";
PreparedStatement prep = prepare(sql);
prep.setInt(1, tableId);
ResultSet rs = prep.executeQuery();
while (rs.next()) {
deleteLob(rs.getLong(1));
}
reuse(sql, prep);
} catch (SQLException e) {
throw DbException.convert(e);
}
......@@ -279,41 +286,52 @@ public class LobStorage {
}
private synchronized PreparedStatement prepare(String sql) throws SQLException {
PreparedStatement prep = prepared.get(sql);
PreparedStatement prep = prepared.remove(sql);
if (prep == null) {
prep = conn.prepareStatement(sql);
prepared.put(sql, prep);
}
return prep;
}
private void deleteLob(long lob) throws SQLException {
PreparedStatement prep;
prep = prepare(
"SELECT BLOCK, HASH FROM " + LOB_MAP + " D WHERE D.LOB = ? " +
private synchronized void reuse(String sql, PreparedStatement prep) {
prepared.put(sql, prep);
}
private synchronized void deleteLob(long lob) throws SQLException {
String sql = "SELECT BLOCK, HASH FROM " + LOB_MAP + " D WHERE D.LOB = ? " +
"AND NOT EXISTS(SELECT 1 FROM " + LOB_MAP + " O " +
"WHERE O.BLOCK = D.BLOCK AND O.LOB <> ?)");
"WHERE O.BLOCK = D.BLOCK AND O.LOB <> ?)";
PreparedStatement prep = prepare(sql);
prep.setLong(1, lob);
prep.setLong(2, lob);
ResultSet rs = prep.executeQuery();
prep = prepare(
"DELETE FROM " + LOB_MAP + " " +
"WHERE LOB = ?");
prep.setLong(1, lob);
prep.execute();
ArrayList<Long> blocks = New.arrayList();
while (rs.next()) {
long block = rs.getLong(1);
blocks.add(rs.getLong(1));
int hash = rs.getInt(2);
setHashCacheBlock(hash, -1);
prep = prepare("DELETE FROM " + LOB_DATA + " WHERE BLOCK = ?");
}
reuse(sql, prep);
sql = "DELETE FROM " + LOB_MAP + " WHERE LOB = ?";
prep = prepare(sql);
prep.setLong(1, lob);
prep.execute();
reuse(sql, prep);
sql = "DELETE FROM " + LOB_DATA + " WHERE BLOCK = ?";
prep = prepare(sql);
for (long block : blocks) {
prep.setLong(1, block);
prep.execute();
}
prep = prepare(
"DELETE FROM " + LOBS + " " +
"WHERE ID = ?");
reuse(sql, prep);
sql = "DELETE FROM " + LOBS + " WHERE ID = ?";
prep = prepare(sql);
prep.setLong(1, lob);
prep.execute();
reuse(sql, prep);
}
/**
......@@ -329,17 +347,17 @@ public class LobStorage {
}
private ValueLobDb addLob(InputStream in, long maxLength, int type) {
try {
byte[] buff = new byte[BLOCK_LENGTH];
if (maxLength < 0) {
maxLength = Long.MAX_VALUE;
}
long length = 0;
long lobId;
long lobId = -1;
int maxLengthInPlaceLob = handler.getMaxLengthInplaceLob();
String compressAlgorithm = handler.getLobCompressionAlgorithm(type);
try {
lobId = getNextLobId();
try {
byte[] small = null;
for (int seq = 0; maxLength > 0; seq++) {
int len = (int) Math.min(BLOCK_LENGTH, maxLength);
len = IOUtils.readFully(in, buff, 0, len);
......@@ -356,15 +374,30 @@ public class LobStorage {
b = buff;
}
if (seq == 0 && b.length < BLOCK_LENGTH && b.length <= maxLengthInPlaceLob) {
// CLOB: the precision will be fixed later
ValueLobDb v = ValueLobDb.createSmallLob(type, b, b.length);
return v;
small = b;
break;
}
synchronized (this) {
if (seq == 0) {
lobId = getNextLobId();
}
storeBlock(lobId, seq, b, compressAlgorithm);
}
}
if (lobId == -1 && small == null) {
// zero length
small = new byte[0];
}
if (small != null) {
// CLOB: the precision will be fixed later
ValueLobDb v = ValueLobDb.createSmallLob(type, small, small.length);
return v;
}
return registerLob(type, lobId, TABLE_TEMP, length);
} catch (IOException e) {
if (lobId != -1) {
deleteLob(lobId);
}
throw DbException.convertIOException(e, "adding blob");
}
} catch (SQLException e) {
......@@ -374,12 +407,13 @@ public class LobStorage {
private ValueLobDb registerLob(int type, long lobId, int tableId, long byteCount) {
try {
PreparedStatement prep = prepare(
"INSERT INTO " + LOBS + "(ID, BYTE_COUNT, TABLE) VALUES(?, ?, ?)");
String sql = "INSERT INTO " + LOBS + "(ID, BYTE_COUNT, TABLE) VALUES(?, ?, ?)";
PreparedStatement prep = prepare(sql);
prep.setLong(1, lobId);
prep.setLong(2, byteCount);
prep.setInt(3, tableId);
prep.execute();
reuse(sql, prep);
ValueLobDb v = ValueLobDb.create(type, this, null, tableId, lobId, byteCount);
return v;
} catch (SQLException e) {
......@@ -396,23 +430,27 @@ public class LobStorage {
* @param length the length
* @return the new lob
*/
public ValueLobDb copyLob(int type, long oldLobId, int tableId, long length) {
public synchronized ValueLobDb copyLob(int type, long oldLobId, int tableId, long length) {
try {
init();
long lobId = getNextLobId();
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_MAP + "(LOB, SEQ, HASH, BLOCK) " +
"SELECT ?, SEQ, HASH, BLOCK FROM " + LOB_MAP + " WHERE LOB = ?");
String sql = "INSERT INTO " + LOB_MAP + "(LOB, SEQ, HASH, BLOCK) " +
"SELECT ?, SEQ, HASH, BLOCK FROM " + LOB_MAP + " WHERE LOB = ?";
PreparedStatement prep = prepare(sql);
prep.setLong(1, lobId);
prep.setLong(2, oldLobId);
prep.executeUpdate();
prep = prepare(
"INSERT INTO " + LOBS + "(ID, BYTE_COUNT, TABLE) " +
"SELECT ?, BYTE_COUNT, ? FROM " + LOBS + " WHERE ID = ?");
reuse(sql, prep);
sql = "INSERT INTO " + LOBS + "(ID, BYTE_COUNT, TABLE) " +
"SELECT ?, BYTE_COUNT, ? FROM " + LOBS + " WHERE ID = ?";
prep = prepare(sql);
prep.setLong(1, lobId);
prep.setLong(2, tableId);
prep.setLong(3, oldLobId);
prep.executeUpdate();
reuse(sql, prep);
ValueLobDb v = ValueLobDb.create(type, this, null, tableId, lobId, length);
return v;
} catch (SQLException e) {
......@@ -456,9 +494,9 @@ public class LobStorage {
int hash = Arrays.hashCode(b);
block = getHashCacheBlock(hash);
if (block != -1) {
PreparedStatement prep = prepare(
"SELECT COMPRESSED, DATA FROM " + LOB_DATA +
" WHERE BLOCK = ?");
String sql = "SELECT COMPRESSED, DATA FROM " + LOB_DATA +
" WHERE BLOCK = ?";
PreparedStatement prep = prepare(sql);
prep.setLong(1, block);
ResultSet rs = prep.executeQuery();
if (rs.next()) {
......@@ -468,24 +506,27 @@ public class LobStorage {
blockExists = true;
}
}
reuse(sql, prep);
}
if (!blockExists) {
block = nextBlock++;
setHashCacheBlock(hash, block);
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_DATA + "(BLOCK, COMPRESSED, DATA) VALUES(?, ?, ?)");
String sql = "INSERT INTO " + LOB_DATA + "(BLOCK, COMPRESSED, DATA) VALUES(?, ?, ?)";
PreparedStatement prep = prepare(sql);
prep.setLong(1, block);
prep.setInt(2, compressAlgorithm == null ? 0 : 1);
prep.setBytes(3, b);
prep.execute();
reuse(sql, prep);
}
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_MAP + "(LOB, SEQ, HASH, BLOCK) VALUES(?, ?, ?, ?)");
String sql = "INSERT INTO " + LOB_MAP + "(LOB, SEQ, HASH, BLOCK) VALUES(?, ?, ?, ?)";
PreparedStatement prep = prepare(sql);
prep.setLong(1, lobId);
prep.setInt(2, seq);
prep.setLong(3, hash);
prep.setLong(4, block);
prep.execute();
reuse(sql, prep);
}
/**
......@@ -611,10 +652,12 @@ public class LobStorage {
public void setTable(long lobId, int table) {
try {
init();
PreparedStatement prep = prepare("UPDATE " + LOBS + " SET TABLE = ? WHERE ID = ?");
String sql = "UPDATE " + LOBS + " SET TABLE = ? WHERE ID = ?";
PreparedStatement prep = prepare(sql);
prep.setInt(1, table);
prep.setLong(2, lobId);
prep.executeUpdate();
reuse(sql, prep);
} catch (SQLException e) {
throw DbException.convert(e);
}
......
......@@ -10,6 +10,7 @@ import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.sql.Blob;
......@@ -28,6 +29,7 @@ import org.h2.constant.SysProperties;
import org.h2.store.FileLister;
import org.h2.test.TestBase;
import org.h2.tools.DeleteDbFiles;
import org.h2.util.Task;
import org.h2.util.Utils;
import org.h2.util.IOUtils;
import org.h2.util.StringUtils;
......@@ -51,6 +53,7 @@ public class TestLob extends TestBase {
}
public void test() throws Exception {
testConcurrentCreate();
testLobInLargeResult();
testUniqueIndex();
testConvert();
......@@ -89,6 +92,44 @@ public class TestLob extends TestBase {
IOUtils.deleteRecursive(TEMP_DIR, true);
}
public void testConcurrentCreate() throws Exception {
deleteDb("lob");
final Connection conn1 = getConnection("lob");
final Connection conn2 = getConnection("lob");
conn1.setAutoCommit(false);
conn2.setAutoCommit(false);
final byte[] buffer = new byte[10000];
Task task1 = new Task() {
public void call() throws Exception {
while (!stop) {
Blob b = conn1.createBlob();
OutputStream out = b.setBinaryStream(1);
out.write(buffer);
out.close();
}
}
};
Task task2 = new Task() {
public void call() throws Exception {
while (!stop) {
Blob b = conn2.createBlob();
OutputStream out = b.setBinaryStream(1);
out.write(buffer);
out.close();
}
}
};
task1.execute();
task2.execute();
Thread.sleep(1000);
task1.get();
task2.get();
conn1.close();
conn2.close();
}
private void testLobInLargeResult() throws Exception {
deleteDb("lob");
Connection conn;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论