提交 1e01a486 authored 作者: Thomas Mueller's avatar Thomas Mueller

Row level locking for MVCC is now enabled.

上级 5ae2eac5
......@@ -191,7 +191,7 @@ public abstract class Command implements CommandInterface {
}
public int executeUpdate() throws SQLException {
startTime = System.currentTimeMillis();
long start = startTime = System.currentTimeMillis();
Database database = session.getDatabase();
database.allocateReserveMemory();
Object sync = database.getMultiThreaded() ? (Object) session : (Object) database;
......@@ -200,15 +200,31 @@ public abstract class Command implements CommandInterface {
int rollback = session.getLogId();
session.setCurrentCommand(this, startTime);
try {
while (true) {
database.checkPowerOff();
try {
return update();
} catch (OutOfMemoryError e) {
database.freeReserveMemory();
throw Message.convert(e);
} catch (SQLException e) {
if (e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1) {
long now = System.currentTimeMillis();
if (now - start > session.getLockTimeout()) {
throw e;
}
try {
database.wait(100);
} catch (InterruptedException e1) {
// ignore
}
continue;
}
throw e;
} catch (Throwable e) {
throw Message.convert(e);
}
}
} catch (SQLException e) {
database.exceptionThrown(e, sql);
database.checkPowerOff();
......
......@@ -341,13 +341,6 @@ public class Constants {
*/
public static final int LOCK_MODE_READ_COMMITTED = 3;
/**
* The lock mode that means row level locks are used if possible.
* This lock mode is similar to read committed, but row level locks are
* used instead of table level locks.
*/
public static final int LOCK_MODE_ROW = 4;
/**
* The lock mode that means table level locking is used for reads and
* writes.
......@@ -367,12 +360,6 @@ public class Constants {
*/
public static final int LOCK_SLEEP = 1000;
/**
* The divider used to calculate the minimum log file size as a function of
* the largest file (data file or index file).
*/
public static final long LOG_SIZE_DIVIDER = 10;
/**
* The file header used for binary files.
*/
......
......@@ -696,7 +696,6 @@ public class JdbcConnection extends TraceObject implements Connection {
transactionIsolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED;
break;
case Constants.LOCK_MODE_READ_COMMITTED:
case Constants.LOCK_MODE_ROW:
transactionIsolationLevel = Connection.TRANSACTION_READ_COMMITTED;
break;
case Constants.LOCK_MODE_TABLE:
......
......@@ -274,7 +274,6 @@ public class TableData extends Table implements RecordReader {
}
public void removeRow(Session session, Row row) throws SQLException {
lastModificationId = database.getNextModificationDataId();
if (database.isMultiVersion()) {
if (row.getDeleted()) {
throw Message.getSQLException(ErrorCode.CONCURRENT_UPDATE_1, getName());
......@@ -287,6 +286,7 @@ public class TableData extends Table implements RecordReader {
throw Message.getSQLException(ErrorCode.CONCURRENT_UPDATE_1, getName());
}
}
lastModificationId = database.getNextModificationDataId();
int i = indexes.size() - 1;
try {
for (; i >= 0; i--) {
......@@ -377,7 +377,7 @@ public class TableData extends Table implements RecordReader {
}
} else {
if (lockExclusive == null) {
if (lockMode == Constants.LOCK_MODE_READ_COMMITTED || lockMode == Constants.LOCK_MODE_ROW) {
if (lockMode == Constants.LOCK_MODE_READ_COMMITTED) {
if (!database.getMultiThreaded() && !database.isMultiVersion()) {
// READ_COMMITTED: a read lock is acquired,
// but released immediately after the operation
......
......@@ -75,6 +75,7 @@ import org.h2.test.jdbcx.TestXASimple;
import org.h2.test.mvcc.TestMvcc1;
import org.h2.test.mvcc.TestMvcc2;
import org.h2.test.mvcc.TestMvcc3;
import org.h2.test.mvcc.TestMvccMultiThreaded;
import org.h2.test.rowlock.TestRowLocks;
import org.h2.test.server.TestNestedLoop;
import org.h2.test.server.TestPgServer;
......@@ -272,6 +273,8 @@ java org.h2.test.TestAll timer
/*
row level locking for mvcc
Run benchmark with the newest versions of other databases.
documentation: use 'server mode' not 'remote mode'.
......@@ -540,6 +543,7 @@ http://www.w3schools.com/sql/
new TestMvcc1().runTest(this);
new TestMvcc2().runTest(this);
new TestMvcc3().runTest(this);
new TestMvccMultiThreaded().runTest(this);
new TestRowLocks().runTest(this);
// synth
......
......@@ -60,6 +60,17 @@ public abstract class TestBase {
config.beforeTest();
}
/**
* Initialize the test configuration using the default settings.
*
* @return itself
*/
public TestBase init() throws Exception {
baseDir = getTestDir("");
this.config = new TestAll();
return this;
}
/**
* Initialize the test configuration.
*
......@@ -483,11 +494,11 @@ public abstract class TestBase {
}
int al = expected.length();
int bl = actual.length();
if (al > 400) {
expected = expected.substring(0, 400);
if (al > 4000) {
expected = expected.substring(0, 4000);
}
if (bl > 400) {
actual = actual.substring(0, 400);
if (bl > 4000) {
actual = actual.substring(0, 4000);
}
fail("Expected: " + expected + " (" + al + ") actual: " + actual + " (" + bl + ")");
}
......
......@@ -18,8 +18,12 @@ import org.h2.test.TestBase;
*/
public class TestRowLocks extends TestBase {
Statement s1, s2;
private Connection c1, c2;
private Statement s1, s2;
public static void main(String[] a) throws Exception {
new TestRowLocks().init().test();
}
public void test() throws Exception {
testSetMode();
......@@ -30,10 +34,10 @@ public class TestRowLocks extends TestBase {
deleteDb("rowLocks");
c1 = getConnection("rowLocks");
Statement stat = c1.createStatement();
stat.execute("SET LOCK_MODE 4");
stat.execute("SET LOCK_MODE 2");
ResultSet rs = stat.executeQuery("call lock_mode()");
rs.next();
assertEquals("4", rs.getString(1));
assertEquals("2", rs.getString(1));
c1.close();
}
......@@ -41,40 +45,57 @@ public class TestRowLocks extends TestBase {
deleteDb("rowLocks");
c1 = getConnection("rowLocks;MVCC=TRUE");
s1 = c1.createStatement();
s1.execute("SET LOCK_MODE 4");
s1.execute("SET LOCK_TIMEOUT 10000");
s1.execute("CREATE TABLE TEST AS SELECT X ID, 'Hello' NAME FROM SYSTEM_RANGE(1, 3)");
c1.commit();
c1.setAutoCommit(false);
s1.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=1");
c2 = getConnection("rowLocks");
c2.setAutoCommit(false);
s2 = c2.createStatement();
ResultSet rs = s1.executeQuery("SELECT NAME FROM TEST WHERE ID=1");
rs.next();
assertEquals("Hallo", rs.getString(1));
rs = s2.executeQuery("SELECT NAME FROM TEST WHERE ID=1");
rs.next();
assertEquals("Hello", rs.getString(1));
assertEquals("Hallo", getSingleValue(s1, "SELECT NAME FROM TEST WHERE ID=1"));
assertEquals("Hello", getSingleValue(s2, "SELECT NAME FROM TEST WHERE ID=1"));
s2.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=2");
try {
s2.executeUpdate("UPDATE TEST SET NAME='Hallo2' WHERE ID=1");
s2.executeUpdate("UPDATE TEST SET NAME='Hi' WHERE ID=1");
fail();
} catch (SQLException e) {
assertKnownException(e);
}
c1.commit();
c2.commit();
rs = s1.executeQuery("SELECT NAME FROM TEST WHERE ID=1");
rs.next();
assertEquals("Hallo", rs.getString(1));
rs = s2.executeQuery("SELECT NAME FROM TEST WHERE ID=1");
rs.next();
assertEquals("Hallo", rs.getString(1));
assertEquals("Hallo", getSingleValue(s1, "SELECT NAME FROM TEST WHERE ID=1"));
assertEquals("Hallo", getSingleValue(s2, "SELECT NAME FROM TEST WHERE ID=1"));
s2.execute("UPDATE TEST SET NAME='H1' WHERE ID=1");
Thread thread = new Thread() {
public void run() {
try {
s1.execute("UPDATE TEST SET NAME='H2' WHERE ID=1");
} catch (SQLException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread.sleep(100);
c2.commit();
thread.join();
c1.commit();
assertEquals("H2", getSingleValue(s1, "SELECT NAME FROM TEST WHERE ID=1"));
assertEquals("H2", getSingleValue(s2, "SELECT NAME FROM TEST WHERE ID=1"));
c1.close();
c2.close();
}
private String getSingleValue(Statement stat, String sql) throws Exception {
ResultSet rs = stat.executeQuery(sql);
return rs.next() ? rs.getString(1) : null;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论