提交 3f5cad84 authored 作者: Noel Grandin's avatar Noel Grandin

Fix multi-threaded mode update exception "NullPointerException"

上级 8ca7992f
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Fix multi-threaded mode update exception "NullPointerException", test case by Anatolii K
</li>
<li>Fix multi-threaded mode insert exception "Unique index or primary key violation", test case by Anatolii K
</li>
<li>Implement ILIKE operator for case-insensitive matching
......
......@@ -167,6 +167,7 @@
90140=The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=Serializer cannot be changed because there is a data table: {0}
90142=Step size must not be zero
90143=Row {1} not found in primary index {0}
HY000=General error: {0}
HY004=Unknown data type: {0}
HYC00=Feature not supported: {0}
......
......@@ -1928,8 +1928,14 @@ public class ErrorCode {
*/
public static final int STEP_SIZE_MUST_NOT_BE_ZERO = 90142;
/**
* The error with code <code>90143</code> is thrown when
* trying to fetch a row from the primary index and the row is not there.
* Can happen in MULTI_THREADED=1 case.
*/
public static final int ROW_NOT_FOUND_IN_PRIMARY_INDEX = 90143;
// next are 90110, 90122, 90143
// next are 90110, 90122, 90144
private ErrorCode() {
// utility class
......
......@@ -299,6 +299,7 @@ public abstract class Command implements CommandInterface {
private long filterConcurrentUpdate(DbException e, long start) {
int errorCode = e.getErrorCode();
if (errorCode != ErrorCode.CONCURRENT_UPDATE_1 &&
errorCode != ErrorCode.ROW_NOT_FOUND_IN_PRIMARY_INDEX &&
errorCode != ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1) {
throw e;
}
......
......@@ -213,6 +213,10 @@ public class MVPrimaryIndex extends BaseIndex {
public Row getRow(Session session, long key) {
TransactionMap<Value, Value> map = getMap(session);
Value v = map.get(ValueLong.get(key));
if (v == null) {
throw DbException.get(ErrorCode.ROW_NOT_FOUND_IN_PRIMARY_INDEX,
getSQL() + ": " + key);
}
ValueArray array = (ValueArray) v;
Row row = session.createRow(array.getList(), 0);
row.setKey(key);
......
......@@ -167,6 +167,7 @@
90140=Vrácený výsledek je pouze pro čtení. Možná budete muset použít conn.createStatement(..., ResultSet.CONCUR_UPDATABLE).
90141=#Serializer cannot be changed because there is a data table: {0}
90142=#Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=Obecná chyba: {0}
HY004=Neznámý datový typ: {0}
HYC00=Vlastnost není podporována: {0}
......
......@@ -167,6 +167,7 @@
90140=Die Resultat-Zeilen können nicht verändert werden. Mögliche Lösung: conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=Serialisierer kann nicht geändert werden wenn eine Daten-Tabelle existiert: {0}
90142=Schrittgrösse darf nicht 0 sein
90143=#Row {1} not found in primary index {0}
HY000=Allgemeiner Fehler: {0}
HY004=Unbekannter Datentyp: {0}
HYC00=Dieses Feature wird nicht unterstützt: {0}
......
......@@ -167,6 +167,7 @@
90140=The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=Serializer cannot be changed because there is a data table: {0}
90142=Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=General error: {0}
HY004=Unknown data type: {0}
HYC00=Feature not supported: {0}
......
......@@ -167,6 +167,7 @@
90140=El conjunto de resultados es de solo lectura. Puede ser necesario usar conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=#Serializer cannot be changed because there is a data table: {0}
90142=#Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=Error General : {0}
HY004=Tipo de dato desconocido : {0}
HYC00=Caracteristica no soportada: {0}
......
......@@ -167,6 +167,7 @@
90140=リザルトセットは読み込み専用です。conn.createStatement(.., ResultSet.CONCUR_UPDATABLE) を使う必要があるかもしれません
90141=データテーブル {0} があるため、シリアライザを変更することはできません
90142=ステップサイズに0は指定できません
90143=#Row {1} not found in primary index {0}
HY000=一般エラー: {0}
HY004=不明なデータ型: {0}
HYC00=機能はサポートされていません: {0}
......
......@@ -167,6 +167,7 @@
90140=Wyniki są tylko do odczytu. Być może potrzebujesz użyć conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=Serializator nie może być zmieniony ponieważ istnieje tabela z danymi: {0}
90142=#Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=Błąd ogólny: {0}
HY004=Nieznany typ danych: {0}
HYC00=Cecha nie jest wspierana: {0}
......
......@@ -167,6 +167,7 @@
90140=#The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=#Serializer cannot be changed because there is a data table: {0}
90142=#Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=Erro geral: {0}
HY004=Tipo de dados desconhecido: {0}
HYC00=Recurso não suportado: {0}
......
......@@ -167,6 +167,7 @@
90140=Набор записей не является обновляемым. Возможно необходимо использовать conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=Serializer не может быть изменен, потому что есть таблица данных: {0}
90142=Размер шага не должен быть равен нулю
90143=#Row {1} not found in primary index {0}
HY000=Внутренняя ошибка: {0}
HY004=Неизвестный тип данных: {0}
HYC00=Данная функция не поддерживается: {0}
......
......@@ -167,6 +167,7 @@
90140=Výsledok (result set) je iba na čítanie. Je potrebné použiť conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=#Serializer cannot be changed because there is a data table: {0}
90142=#Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=Všeobecná chyba: {0}
HY004=Neznámy dátový typ: {0}
HYC00=Vlastnosť nie je podporovaná: {0}
......
......@@ -167,6 +167,7 @@
90140=结果集是只读的. 你可以使用 conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=#Serializer cannot be changed because there is a data table: {0}
90142=#Step size must not be zero
90143=#Row {1} not found in primary index {0}
HY000=常规错误: {0}
HY004=位置数据类型: {0}
HYC00=不支持的特性: {0}
......
package org.h2.test;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Bug_H2_1 {
static String dbUrl = "jdbc:h2:nioMemFS:dbbug1;DB_CLOSE_DELAY=-1;MULTI_THREADED=1";
static String user = "sa", pwd = "";
static int threadCount = 100;
public static void main(String args[]) throws InterruptedException, SQLException {
Arte2[] artes = new Arte2[threadCount];
Connection dbConnect;
try {
Class.forName("org.h2.Driver");
dbConnect = DriverManager.getConnection(dbUrl, user, pwd);
DbPreparator2.prepareScheme(dbConnect);
System.out.println("DB scheme prepared");
DbPreparator2.populate(dbConnect);
System.out.println("DB populated");
for (int i = 0; i < threadCount; i++) {
artes[i] = new Arte2(DriverManager.getConnection(dbUrl, user, pwd));
}
System.out.println("ARTEs created");
} catch (ClassNotFoundException | SQLException e) {
System.out.println("DB Connection Failed: " + dbUrl);
e.printStackTrace();
return;
}
ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
threadPool.submit(artes[i]);
}
while (true) {
Thread.sleep(100);
}
}
}
class DbPreparator2 {
public static final int OBJ_CNT = 10000;
private static final String[] SQLs = new String[] { "CREATE TABLE IF NOT EXISTS ACCOUNT ("
+ " ID NUMBER(18,0) not null PRIMARY KEY, BALANCE NUMBER null)" };
public static void prepareScheme(Connection db) throws SQLException {
for (String sql : SQLs) {
db.createStatement().execute(sql);
db.commit();
}
}
public static void populate(Connection db) throws SQLException {
PreparedStatement mergeAcctStmt = db.prepareStatement("MERGE INTO Account(id, balance) key (id) VALUES (?, ?)");
for (int i = 0; i < OBJ_CNT; i++) {
mergeAcctStmt.setLong(1, i);
mergeAcctStmt.setBigDecimal(2, BigDecimal.ZERO);
mergeAcctStmt.addBatch();
}
mergeAcctStmt.executeBatch();
db.commit();
}
}
class Arte2 implements Callable<Integer> {
Connection db;
PreparedStatement updateAcctStmt;
public Arte2(Connection db_) throws SQLException {
db = db_;
db.setAutoCommit(false);
updateAcctStmt = db.prepareStatement("UPDATE account set balance = ? where id = ?");
}
@Override
public Integer call() {
try {
while (true) {
updateAcctStmt.setDouble(1, Math.random());
updateAcctStmt.setLong(2, (int) (Math.random() * DbPreparator2.OBJ_CNT));
updateAcctStmt.execute();
db.commit();
}
} catch (SQLException e) {
System.out.println("DB write error: ");
e.printStackTrace();
System.exit(0);
}
return null;
}
}
package org.h2.test;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
public class QueryTest {
static String dbUrl = "jdbc:h2:~/db1";
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection(dbUrl, "sa", "");
InputStream is = QueryTest.class.getResourceAsStream("query.txt");
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
String sql = s.next();
long time1 = System.nanoTime();
conn.prepareStatement(sql);
long time2 = System.nanoTime();
System.out.println("time = " + (time2-time1)/1000/1000);
}
}
......@@ -6,6 +6,7 @@
package org.h2.test.db;
import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
......@@ -72,6 +73,7 @@ public class TestMultiThread extends TestBase implements Runnable {
testLockModeWithMultiThreaded();
testViews();
testConcurrentInsert();
testConcurrentUpdate();
}
private void testConcurrentSchemaChange() throws Exception {
......@@ -431,4 +433,61 @@ public class TestMultiThread extends TestBase implements Runnable {
deleteDb("lockMode");
}
private void testConcurrentUpdate() throws Exception {
deleteDb("lockMode");
final int OBJ_CNT = 10000;
final String url = getURL("lockMode;MULTI_THREADED=1;LOCK_TIMEOUT=10000", true);
final Connection conn = getConnection(url);
conn.createStatement().execute(
"CREATE TABLE IF NOT EXISTS ACCOUNT ( ID NUMBER(18,0) not null PRIMARY KEY, BALANCE NUMBER null)");
final PreparedStatement mergeAcctStmt = conn
.prepareStatement("MERGE INTO Account(id, balance) key (id) VALUES (?, ?)");
for (int i = 0; i < OBJ_CNT; i++) {
mergeAcctStmt.setLong(1, i);
mergeAcctStmt.setBigDecimal(2, BigDecimal.ZERO);
mergeAcctStmt.execute();
}
final int threadCount = 100;
final ArrayList<Callable<Void>> callables = new ArrayList<Callable<Void>>();
for (int i = 0; i < threadCount; i++) {
final Connection taskConn = getConnection(url);
taskConn.setAutoCommit(false);
final PreparedStatement updateAcctStmt = taskConn
.prepareStatement("UPDATE account set balance = ? where id = ?");
callables.add(new Callable<Void>() {
@Override
public Void call() throws Exception {
for (int j = 0; j < 10000; j++) {
updateAcctStmt.setDouble(1, Math.random());
updateAcctStmt.setLong(2, (int) (Math.random() * OBJ_CNT));
updateAcctStmt.execute();
taskConn.commit();
}
taskConn.close();
return null;
}
});
}
final ExecutorService executor = Executors.newFixedThreadPool(threadCount);
try {
final ArrayList<Future<Void>> jobs = new ArrayList<Future<Void>>();
for (int i = 0; i < threadCount; i++) {
jobs.add(executor.submit(callables.get(i)));
}
// check for exceptions
for (Future<Void> job : jobs) {
job.get(5, TimeUnit.MINUTES);
}
} finally {
conn.close();
executor.shutdown();
executor.awaitTermination(20, TimeUnit.SECONDS);
}
deleteDb("lockMode");
}
}
INSERT INTO "DB" ("AC",
"AB",
"AA")
SELECT ROWNUM() "I",
"ID",
?
FROM
(SELECT "ID"
FROM
(SELECT MIN("I") "I",
"ID"
FROM
(SELECT ROWNUM() "I",
"ID"
FROM
(SELECT "ID"
FROM
(SELECT "T"."AA" "ID"
FROM "AE" "T"
INNER JOIN "AE" "T101" ON ("T101"."AA"="T"."AA")
INNER JOIN "CS" "T102" ON ("T102"."AA"="T101"."AD")
INNER JOIN "CP" "T103" ON ("T103"."AA"="T102"."AD")
LEFT JOIN "BG" "T104" ON ("T104"."AA"="T103"."AB")
INNER JOIN "AE" "T105" ON ("T105"."AA"="T101"."AA")
INNER JOIN "AE" "T106" ON ("T106"."AA"="T105"."AA")
LEFT JOIN "EB" "T108" ON ("T108"."AA"="T106"."AA")
LEFT JOIN "DV" "T107" ON ("T107"."AA"="T108"."AB")
LEFT JOIN "EB" "T106A" ON ("T106A"."AA"="T106"."AA")
INNER JOIN "AE" "T109" ON ("T109"."AA"="T105"."AA")
LEFT JOIN "EB" "T112" ON ("T112"."AA"="T109"."AA")
LEFT JOIN "DV" "T110" ON ("T110"."AA"="T112"."AB")
LEFT JOIN "EA" "T111" ON ("T111"."AA"="T110"."AA")
LEFT JOIN "EB" "T109A" ON ("T109A"."AA"="T109"."AA")
LEFT JOIN "EB" "T105A" ON ("T105A"."AA"="T105"."AA")
INNER JOIN "AE" "T113" ON ("T113"."AA"="T101"."AA")
INNER JOIN "AE" "T114" ON ("T114"."AA"="T113"."AA")
LEFT JOIN "EB" "T114A" ON ("T114A"."AA"="T114"."AA")
INNER JOIN "AE" "T122" ON ("T122"."AA"="T113"."AA")
LEFT JOIN "EB" "T122A" ON ("T122A"."AA"="T122"."AA")
INNER JOIN "AE" "T130" ON ("T130"."AA"="T113"."AA")
LEFT JOIN "EB" "T130A" ON ("T130A"."AA"="T130"."AA")
INNER JOIN "AE" "T134" ON ("T134"."AA"="T113"."AA")
LEFT JOIN "EB" "T134A" ON ("T134A"."AA"="T134"."AA")
LEFT JOIN "EB" "T113A" ON ("T113A"."AA"="T113"."AA")
LEFT JOIN "EB" "T101A" ON ("T101A"."AA"="T101"."AA")
WHERE "T101A"."AA" IS NOT NULL
AND "T101A"."AA" IS NOT NULL
AND "T104"."AF"=?
AND "T104"."AH"=?
AND "T104"."AI"=?
AND "T101A"."AA" IS NOT NULL
AND "T101A"."AA" IS NOT NULL
AND "T101"."AN"=?
AND "T101A"."AA" IS NOT NULL
AND (("T105A"."AA" IS NOT NULL
AND (("T106A"."AA" IS NOT NULL
AND "T107"."AA"=?)
OR ("T109A"."AA" IS NOT NULL
AND "T110"."AJ"=?
AND "T111"."AA" IS NOT NULL
AND "T111"."AB"=?)))
OR ("T113A"."AA" IS NOT NULL
AND "T113A"."AA" IS NOT NULL
AND (("T114A"."AA" IS NOT NULL
AND "T114"."AA" IN
(SELECT "T118"."AA" "T118AA"
FROM "AE" "T118"
INNER JOIN "CS" "T117" ON ("T117"."AA"="T118"."AD")
INNER JOIN "AH" "T116" ON ("T116"."AE"="T118"."AA")
INNER JOIN "BQ" "T115" ON ("T115"."AA"="T116"."AF")
WHERE "T115"."AA" IN
(SELECT "T119"."AB" "T119AB"
FROM "DX" "T119"
INNER JOIN "AA" "T121" ON ("T121"."AB"="T119"."AA")
INNER JOIN "DV" "T120" ON ("T120"."AA"="T121"."AC")
WHERE "T120"."AA"=?)
AND "T116"."AI"=?))
OR ("T122A"."AA" IS NOT NULL
AND "T122"."AA" IN
(SELECT "T126"."AA" "T126AA"
FROM "AE" "T126"
INNER JOIN "CS" "T125" ON ("T125"."AA"="T126"."AD")
INNER JOIN "AH" "T124" ON ("T124"."AE"="T126"."AA")
INNER JOIN "DK" "T123" ON ("T123"."AA"="T124"."AG")
WHERE "T123"."AA" IN
(SELECT "T127"."AB" "T127AB"
FROM "DZ" "T127"
INNER JOIN "AA" "T129" ON ("T129"."AB"="T127"."AA")
INNER JOIN "DV" "T128" ON ("T128"."AA"="T129"."AC")
WHERE "T128"."AA"=?)
AND "T124"."AI"=?))
OR ("T130A"."AA" IS NOT NULL
AND "T130"."AA" IN
(SELECT "T132"."AE" "T132AE"
FROM "AH" "T132"
INNER JOIN "AA" "T133" ON ("T133"."AB"="T132"."AH")
INNER JOIN "DV" "T131" ON ("T131"."AA"="T133"."AC")
WHERE "T131"."AA"=?
AND "T132"."AI"=?)))
AND "T113A"."AA" IS NOT NULL
AND NOT ("T134A"."AA" IS NOT NULL
AND "T134A"."AA" IS NOT NULL
AND "T134"."AA" IN
(SELECT "T136"."AE" "T136AE"
FROM "AH" "T136"
INNER JOIN "DV" "T135" ON ("T135"."AA"="T136"."AH")
WHERE "T135"."AA"=?
AND "T136"."AI"=?)
AND "T134A"."AA" IS NOT NULL
AND "T134"."AA" IN
(SELECT "T140"."AA" "T140AA"
FROM "AE" "T140"
INNER JOIN "CS" "T139" ON ("T139"."AA"="T140"."AD")
INNER JOIN "AH" "T138" ON ("T138"."AE"="T140"."AA")
INNER JOIN "BQ" "T137" ON ("T137"."AA"="T138"."AF")
WHERE "T137"."AA" IN
(SELECT "T141"."AB" "T141AB"
FROM "DX" "T141"
INNER JOIN "DV" "T142" ON ("T142"."AA"="T141"."AA")
WHERE "T142"."AA"=?)
AND "T138"."AI"=?)
AND "T134A"."AA" IS NOT NULL
AND "T134"."AA" IN
(SELECT "T146"."AA" "T146AA"
FROM "AE" "T146"
INNER JOIN "CS" "T145" ON ("T145"."AA"="T146"."AD")
INNER JOIN "AH" "T144" ON ("T144"."AE"="T146"."AA")
INNER JOIN "DK" "T143" ON ("T143"."AA"="T144"."AG")
WHERE "T143"."AA" IN
(SELECT "T147"."AB" "T147AB"
FROM "DZ" "T147"
INNER JOIN "DV" "T148" ON ("T148"."AA"="T147"."AA")
WHERE "T148"."AA"=?)
AND "T144"."AI"=?))
AND "T113A"."AA" IS NOT NULL
AND "T113A"."AB" IS NULL))
ORDER BY "T"."AA") "t2") "t3") "t4"
GROUP BY "ID"
ORDER BY "I") "t5") "t7"
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论