Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
de64ab1a
提交
de64ab1a
authored
4月 12, 2013
作者:
Thomas Mueller
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
MVStore table engine (WIP)
上级
237b1a55
隐藏空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
213 行增加
和
1234 行删除
+213
-1234
Session.java
h2/src/main/org/h2/engine/Session.java
+3
-3
DataUtils.java
h2/src/main/org/h2/mvstore/DataUtils.java
+6
-2
MVDelegateIndex.java
h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java
+0
-2
MVPrimaryIndex.java
h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java
+3
-8
MVSecondaryIndex.java
h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java
+4
-11
MVTable.java
h2/src/main/org/h2/mvstore/db/MVTable.java
+13
-33
MVTableEngine.java
h2/src/main/org/h2/mvstore/db/MVTableEngine.java
+8
-6
TransactionStore.java
h2/src/main/org/h2/mvstore/db/TransactionStore.java
+120
-134
TransactionStore2.java
h2/src/main/org/h2/mvstore/db/TransactionStore2.java
+0
-1015
TestMVTableEngine.java
h2/src/test/org/h2/test/store/TestMVTableEngine.java
+44
-8
TestTransactionStore.java
h2/src/test/org/h2/test/store/TestTransactionStore.java
+12
-12
没有找到文件。
h2/src/main/org/h2/engine/Session.java
浏览文件 @
de64ab1a
...
...
@@ -23,8 +23,8 @@ import org.h2.jdbc.JdbcConnection;
import
org.h2.message.DbException
;
import
org.h2.message.Trace
;
import
org.h2.message.TraceSystem
;
import
org.h2.mvstore.db.TransactionStore
2
;
import
org.h2.mvstore.db.TransactionStore
2
.Transaction
;
import
org.h2.mvstore.db.TransactionStore
;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.result.ResultInterface
;
import
org.h2.result.Row
;
import
org.h2.schema.Schema
;
...
...
@@ -1257,7 +1257,7 @@ public class Session extends SessionWithState {
* @param store the store
* @return the transaction
*/
public
Transaction
getTransaction
(
TransactionStore
2
store
)
{
public
Transaction
getTransaction
(
TransactionStore
store
)
{
if
(
transaction
==
null
)
{
transaction
=
store
.
begin
();
startStatement
=
-
1
;
...
...
h2/src/main/org/h2/mvstore/DataUtils.java
浏览文件 @
de64ab1a
...
...
@@ -76,6 +76,11 @@ public class DataUtils {
*/
private
static
final
byte
[]
EMPTY_BYTES
=
{};
/**
* The maximum byte to grow a buffer at a time.
*/
private
static
final
int
MAX_GROW
=
16
*
1024
*
1024
;
/**
* Get the length of the variable size int.
*
...
...
@@ -739,8 +744,7 @@ public class DataUtils {
private
static
ByteBuffer
grow
(
ByteBuffer
buff
,
int
len
)
{
len
=
buff
.
remaining
()
+
len
;
int
capacity
=
buff
.
capacity
();
// grow at most 1 MB at a time
len
=
Math
.
max
(
len
,
Math
.
min
(
capacity
+
1024
*
1024
,
capacity
*
2
));
len
=
Math
.
max
(
len
,
Math
.
min
(
capacity
+
MAX_GROW
,
capacity
*
2
));
ByteBuffer
temp
=
ByteBuffer
.
allocate
(
len
);
buff
.
flip
();
temp
.
put
(
buff
);
...
...
h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java
浏览文件 @
de64ab1a
...
...
@@ -35,9 +35,7 @@ public class MVDelegateIndex extends BaseIndex {
}
}
static
int
count
;
public
void
add
(
Session
session
,
Row
row
)
{
count
++;
// nothing to do
}
...
...
h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java
浏览文件 @
de64ab1a
...
...
@@ -18,8 +18,8 @@ import org.h2.index.Cursor;
import
org.h2.index.IndexType
;
import
org.h2.message.DbException
;
import
org.h2.mvstore.MVMap
;
import
org.h2.mvstore.db.TransactionStore
2
.Transaction
;
import
org.h2.mvstore.db.TransactionStore
2
.TransactionMap
;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.mvstore.db.TransactionStore.TransactionMap
;
import
org.h2.result.Row
;
import
org.h2.result.SearchRow
;
import
org.h2.result.SortOrder
;
...
...
@@ -59,7 +59,6 @@ public class MVPrimaryIndex extends BaseIndex {
valueType
(
valueType
);
dataMap
=
mvTable
.
getTransaction
(
null
).
openMap
(
mapName
,
mapBuilder
);
Value
k
=
dataMap
.
lastKey
();
dataMap
.
getTransaction
().
commit
();
lastKey
=
k
==
null
?
0
:
k
.
getLong
();
}
...
...
@@ -73,7 +72,6 @@ public class MVPrimaryIndex extends BaseIndex {
rename
(
newName
+
"_DATA"
);
String
newMapName
=
newName
+
"_DATA_"
+
getId
();
map
.
renameMap
(
newMapName
);
map
.
getTransaction
().
commit
();
mapName
=
newMapName
;
}
...
...
@@ -233,10 +231,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public
long
getRowCountApproximation
()
{
TransactionMap
<
Value
,
Value
>
map
=
getMap
(
null
);
long
size
=
map
.
getSize
();
map
.
getTransaction
().
commit
();
return
size
;
return
getMap
(
null
).
getSize
();
}
public
long
getDiskSpaceUsed
()
{
...
...
h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java
浏览文件 @
de64ab1a
...
...
@@ -17,8 +17,8 @@ import org.h2.index.Cursor;
import
org.h2.index.IndexType
;
import
org.h2.message.DbException
;
import
org.h2.mvstore.MVMap
;
import
org.h2.mvstore.db.TransactionStore
2
.Transaction
;
import
org.h2.mvstore.db.TransactionStore
2
.TransactionMap
;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.mvstore.db.TransactionStore.TransactionMap
;
import
org.h2.result.Row
;
import
org.h2.result.SearchRow
;
import
org.h2.result.SortOrder
;
...
...
@@ -87,7 +87,6 @@ public class MVSecondaryIndex extends BaseIndex {
TransactionMap
<
Value
,
Value
>
map
=
getMap
(
null
);
String
newMapName
=
newName
+
"_"
+
getId
();
map
.
renameMap
(
newMapName
);
map
.
getTransaction
().
commit
();
mapName
=
newMapName
;
super
.
rename
(
newName
);
}
...
...
@@ -218,10 +217,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public
boolean
needRebuild
()
{
TransactionMap
<
Value
,
Value
>
map
=
getMap
(
null
);
boolean
result
=
map
.
getSize
()
==
0
;
map
.
getTransaction
().
commit
();
return
result
;
return
getMap
(
null
).
getSize
()
==
0
;
}
@Override
...
...
@@ -232,10 +228,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public
long
getRowCountApproximation
()
{
TransactionMap
<
Value
,
Value
>
map
=
getMap
(
null
);
long
size
=
map
.
getSize
();
map
.
getTransaction
().
commit
();
return
size
;
return
getMap
(
null
).
getSize
();
}
public
long
getDiskSpaceUsed
()
{
...
...
h2/src/main/org/h2/mvstore/db/MVTable.java
浏览文件 @
de64ab1a
...
...
@@ -28,7 +28,7 @@ import org.h2.index.IndexType;
import
org.h2.index.MultiVersionIndex
;
import
org.h2.message.DbException
;
import
org.h2.message.Trace
;
import
org.h2.mvstore.db.TransactionStore
2
.Transaction
;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.result.Row
;
import
org.h2.result.SortOrder
;
import
org.h2.schema.SchemaObject
;
...
...
@@ -48,7 +48,7 @@ import org.h2.value.Value;
public
class
MVTable
extends
TableBase
{
private
final
String
storeName
;
private
final
TransactionStore
2
store
;
private
final
TransactionStore
store
;
private
MVPrimaryIndex
primaryIndex
;
private
ArrayList
<
Index
>
indexes
=
New
.
arrayList
();
private
long
lastModificationId
;
...
...
@@ -68,7 +68,7 @@ public class MVTable extends TableBase {
*/
private
boolean
waitForLock
;
public
MVTable
(
CreateTableData
data
,
String
storeName
,
TransactionStore
2
store
)
{
public
MVTable
(
CreateTableData
data
,
String
storeName
,
TransactionStore
store
)
{
super
(
data
);
nextAnalyze
=
database
.
getSettings
().
analyzeAuto
;
this
.
storeName
=
storeName
;
...
...
@@ -485,26 +485,16 @@ public class MVTable extends TableBase {
@Override
public
void
removeRow
(
Session
session
,
Row
row
)
{
lastModificationId
=
database
.
getNextModificationDataId
();
int
i
=
indexes
.
size
()
-
1
;
Transaction
t
=
getTransaction
(
session
);
long
savepoint
=
t
.
setSavepoint
();
try
{
for
(;
i
>=
0
;
i
--)
{
for
(
int
i
=
indexes
.
size
()
-
1
;
i
>=
0
;
i
--)
{
Index
index
=
indexes
.
get
(
i
);
index
.
remove
(
session
,
row
);
}
rowCount
--;
}
catch
(
Throwable
e
)
{
try
{
while
(++
i
<
indexes
.
size
())
{
Index
index
=
indexes
.
get
(
i
);
index
.
add
(
session
,
row
);
}
}
catch
(
DbException
e2
)
{
// this could happen, for example on failure in the storage
// but if that is not the case it means there is something wrong
// with the database
trace
.
error
(
e2
,
"could not undo operation"
);
throw
e2
;
}
t
.
rollbackToSavepoint
(
savepoint
);
throw
DbException
.
convert
(
e
);
}
analyzeIfRequired
(
session
);
...
...
@@ -524,26 +514,16 @@ public class MVTable extends TableBase {
@Override
public
void
addRow
(
Session
session
,
Row
row
)
{
lastModificationId
=
database
.
getNextModificationDataId
();
int
i
=
0
;
Transaction
t
=
getTransaction
(
session
);
long
savepoint
=
t
.
setSavepoint
();
try
{
for
(
int
size
=
indexes
.
size
();
i
<
size
;
i
++)
{
for
(
int
i
=
0
,
size
=
indexes
.
size
();
i
<
size
;
i
++)
{
Index
index
=
indexes
.
get
(
i
);
index
.
add
(
session
,
row
);
}
rowCount
++;
}
catch
(
Throwable
e
)
{
try
{
while
(--
i
>=
0
)
{
Index
index
=
indexes
.
get
(
i
);
index
.
remove
(
session
,
row
);
}
}
catch
(
DbException
e2
)
{
// this could happen, for example on failure in the storage
// but if that is not the case it means there is something wrong
// with the database
trace
.
error
(
e2
,
"could not undo operation"
);
throw
e2
;
}
t
.
rollbackToSavepoint
(
savepoint
);
DbException
de
=
DbException
.
convert
(
e
);
if
(
de
.
getErrorCode
()
==
ErrorCode
.
DUPLICATE_KEY_1
)
{
for
(
int
j
=
0
;
j
<
indexes
.
size
();
j
++)
{
...
...
@@ -572,7 +552,7 @@ public class MVTable extends TableBase {
}
int
rows
=
session
.
getDatabase
().
getSettings
().
analyzeSample
;
int
test
;
//
Analyze.analyzeTable(session, this, rows, false);
//
Analyze.analyzeTable(session, this, rows, false);
}
@Override
...
...
@@ -687,7 +667,7 @@ int test;
return
session
.
getTransaction
(
store
);
}
public
TransactionStore
2
getStore
()
{
public
TransactionStore
getStore
()
{
return
store
;
}
...
...
h2/src/main/org/h2/mvstore/db/MVTableEngine.java
浏览文件 @
de64ab1a
...
...
@@ -42,7 +42,6 @@ public class MVTableEngine implements TableEngine {
if
(
store
==
null
)
{
return
;
}
// TODO this stores uncommitted transactions as well
store
(
store
.
getStore
());
}
}
...
...
@@ -115,8 +114,11 @@ public class MVTableEngine implements TableEngine {
* @param store the store
*/
static
void
store
(
MVStore
store
)
{
store
.
compact
(
50
);
store
.
store
();
if
(!
store
.
isReadOnly
())
{
store
.
commit
();
store
.
compact
(
50
);
store
.
store
();
}
}
/**
...
...
@@ -142,12 +144,12 @@ public class MVTableEngine implements TableEngine {
/**
* The transaction store.
*/
private
final
TransactionStore
2
transactionStore
;
private
final
TransactionStore
transactionStore
;
public
Store
(
Database
db
,
MVStore
store
)
{
this
.
db
=
db
;
this
.
store
=
store
;
this
.
transactionStore
=
new
TransactionStore
2
(
store
,
this
.
transactionStore
=
new
TransactionStore
(
store
,
new
ValueDataType
(
null
,
null
,
null
));
}
...
...
@@ -155,7 +157,7 @@ public class MVTableEngine implements TableEngine {
return
store
;
}
public
TransactionStore
2
getTransactionStore
()
{
public
TransactionStore
getTransactionStore
()
{
return
transactionStore
;
}
...
...
h2/src/main/org/h2/mvstore/db/TransactionStore.java
浏览文件 @
de64ab1a
...
...
@@ -16,9 +16,8 @@ import java.util.Map;
import
org.h2.mvstore.Cursor
;
import
org.h2.mvstore.DataUtils
;
import
org.h2.mvstore.MVMap
;
import
org.h2.mvstore.MVMapConcurrent
;
import
org.h2.mvstore.MVStore
;
import
org.h2.mvstore.MVMap.Builder
;
import
org.h2.mvstore.MVStore
;
import
org.h2.mvstore.type.DataType
;
import
org.h2.mvstore.type.ObjectDataType
;
import
org.h2.util.New
;
...
...
@@ -30,6 +29,8 @@ public class TransactionStore {
private
static
final
String
LAST_TRANSACTION_ID
=
"lastTransactionId"
;
private
static
final
int
MAX_UNSAVED_PAGES
=
4
*
1024
;
/**
* The store.
*/
...
...
@@ -49,7 +50,7 @@ public class TransactionStore {
/**
* The undo log.
* Key: [ transactionId, logId ], value: [
baseVersion, mapId, key
].
* Key: [ transactionId, logId ], value: [
opType, mapId, key, oldValue
].
*/
final
MVMap
<
long
[],
Object
[]>
undoLog
;
...
...
@@ -86,14 +87,15 @@ public class TransactionStore {
this
.
store
=
store
;
settings
=
store
.
openMap
(
"settings"
);
openTransactions
=
store
.
openMap
(
"openTransactions"
,
new
MVMap
Concurrent
.
Builder
<
Long
,
Object
[]>());
new
MVMap
.
Builder
<
Long
,
Object
[]>());
// commit could be faster if we have one undo log per transaction,
// or a range delete operation for maps
ArrayType
valueType
=
new
ArrayType
(
new
DataType
[]{
new
ObjectDataType
(),
new
ObjectDataType
(),
keyType
new
ObjectDataType
(),
new
ObjectDataType
(),
keyType
,
new
ObjectDataType
()
});
MVMap
Concurrent
.
Builder
<
long
[],
Object
[]>
builder
=
new
MVMap
Concurrent
.
Builder
<
long
[],
Object
[]>().
MVMap
.
Builder
<
long
[],
Object
[]>
builder
=
new
MVMap
.
Builder
<
long
[],
Object
[]>().
valueType
(
valueType
);
// TODO escape other map names, to avoid conflicts
undoLog
=
store
.
openMap
(
"undoLog"
,
builder
);
...
...
@@ -153,13 +155,15 @@ public class TransactionStore {
* @return the transaction
*/
public
synchronized
Transaction
begin
()
{
store
.
incrementVersion
();
long
transactionId
=
lastTransactionId
++;
int
status
=
Transaction
.
STATUS_OPEN
;
return
new
Transaction
(
this
,
transactionId
,
status
,
null
,
0
);
}
private
void
storeTransaction
(
Transaction
t
)
{
if
(
store
.
getUnsavedPageCount
()
>
MAX_UNSAVED_PAGES
)
{
store
.
commit
();
}
long
transactionId
=
t
.
getId
();
if
(
openTransactions
.
containsKey
(
transactionId
))
{
return
;
...
...
@@ -194,12 +198,13 @@ public class TransactionStore {
* @param opType the operation type
* @param mapId the map id
* @param key the key
* @param oldValue the old value
*/
void
log
(
Transaction
t
,
long
logId
,
int
opType
,
int
mapId
,
Object
key
)
{
Object
key
,
Object
oldValue
)
{
storeTransaction
(
t
);
long
[]
undoKey
=
{
t
.
getId
(),
logId
};
Object
[]
log
=
new
Object
[]
{
opType
,
mapId
,
key
};
Object
[]
log
=
new
Object
[]
{
opType
,
mapId
,
key
,
oldValue
};
undoLog
.
put
(
undoKey
,
log
);
}
...
...
@@ -214,7 +219,6 @@ public class TransactionStore {
Object
[]
old
=
openTransactions
.
get
(
t
.
getId
());
Object
[]
v
=
{
old
[
0
],
name
};
openTransactions
.
put
(
t
.
getId
(),
v
);
store
.
commit
();
}
/**
...
...
@@ -224,7 +228,6 @@ public class TransactionStore {
* @param maxLogId the last log id
*/
void
commit
(
Transaction
t
,
long
maxLogId
)
{
store
.
incrementVersion
();
for
(
long
logId
=
0
;
logId
<
maxLogId
;
logId
++)
{
long
[]
undoKey
=
new
long
[]
{
t
.
getId
(),
logId
};
...
...
@@ -247,17 +250,7 @@ public class TransactionStore {
}
undoLog
.
remove
(
undoKey
);
}
openTransactions
.
remove
(
t
.
getId
());
openTransactionMap
.
remove
(
t
.
getId
());
store
.
commit
();
long
oldestVersion
=
store
.
getCurrentVersion
();
for
(
Transaction
u
:
openTransactionMap
.
values
())
{
long
v
=
u
.
startVersion
;
if
(
v
<
oldestVersion
)
{
oldestVersion
=
v
;
}
}
store
.
setRetainVersion
(
oldestVersion
);
endTransaction
(
t
);
}
/**
...
...
@@ -268,9 +261,12 @@ public class TransactionStore {
*/
void
rollback
(
Transaction
t
,
long
maxLogId
)
{
rollbackTo
(
t
,
maxLogId
,
0
);
endTransaction
(
t
);
}
private
void
endTransaction
(
Transaction
t
)
{
openTransactions
.
remove
(
t
.
getId
());
openTransactionMap
.
remove
(
t
.
getId
());
store
.
commit
();
}
/**
...
...
@@ -281,7 +277,6 @@ public class TransactionStore {
* @param toLogId the log id to roll back to
*/
void
rollbackTo
(
Transaction
t
,
long
maxLogId
,
long
toLogId
)
{
store
.
incrementVersion
();
for
(
long
logId
=
maxLogId
-
1
;
logId
>=
toLogId
;
logId
--)
{
Object
[]
op
=
undoLog
.
get
(
new
long
[]
{
t
.
getId
(),
logId
});
...
...
@@ -291,29 +286,16 @@ public class TransactionStore {
String
mapName
=
DataUtils
.
parseMap
(
m
).
get
(
"name"
);
MVMap
<
Object
,
Object
[]>
map
=
store
.
openMap
(
mapName
);
Object
key
=
op
[
2
];
Object
[]
value
=
map
.
get
(
key
);
if
(
value
!=
null
)
{
Long
oldVersion
=
(
Long
)
value
[
1
];
if
(
oldVersion
==
null
)
{
// this transaction added the value
map
.
remove
(
key
);
}
else
if
(
oldVersion
<
map
.
getCreateVersion
())
{
map
.
remove
(
key
);
}
else
{
// this transaction updated the value
MVMap
<
Object
,
Object
[]>
mapOld
=
map
.
openVersion
(
oldVersion
);
Object
[]
old
=
mapOld
.
get
(
key
);
if
(
old
==
null
)
{
map
.
remove
(
key
);
}
else
{
map
.
put
(
key
,
old
);
}
}
Object
[]
oldValue
=
(
Object
[])
op
[
3
];
if
(
oldValue
==
null
)
{
// this transaction added the value
map
.
remove
(
key
);
}
else
{
// this transaction updated the value
map
.
put
(
key
,
oldValue
);
}
undoLog
.
remove
(
logId
);
undoLog
.
remove
(
op
);
}
store
.
commit
();
}
/**
...
...
@@ -354,14 +336,14 @@ public class TransactionStore {
/**
* The transaction id.
*/
final
l
ong
transactionId
;
final
L
ong
transactionId
;
long
logId
;
private
int
status
;
private
String
name
;
private
long
logId
;
Transaction
(
TransactionStore
store
,
long
transactionId
,
int
status
,
String
name
,
long
logId
)
{
this
.
store
=
store
;
this
.
startVersion
=
store
.
store
.
getCurrentVersion
();
...
...
@@ -376,7 +358,7 @@ public class TransactionStore {
*
* @return the transaction id
*/
public
l
ong
getId
()
{
public
L
ong
getId
()
{
return
transactionId
;
}
...
...
@@ -416,7 +398,6 @@ public class TransactionStore {
*/
public
long
setSavepoint
()
{
checkOpen
();
store
.
store
.
incrementVersion
();
return
logId
;
}
...
...
@@ -426,13 +407,14 @@ public class TransactionStore {
* @param opType the operation type
* @param mapId the map id
* @param key the key
* @param oldValue the old value
*/
void
log
(
int
opType
,
int
mapId
,
Object
key
)
{
store
.
log
(
this
,
logId
++,
opType
,
mapId
,
key
);
void
log
(
int
opType
,
int
mapId
,
Object
key
,
Object
oldValue
)
{
store
.
log
(
this
,
logId
++,
opType
,
mapId
,
key
,
oldValue
);
}
/**
* Open a data map
where reads are always up to date
.
* Open a data map.
*
* @param <K> the key type
* @param <V> the value type
...
...
@@ -440,22 +422,9 @@ public class TransactionStore {
* @return the transaction map
*/
public
<
K
,
V
>
TransactionMap
<
K
,
V
>
openMap
(
String
name
)
{
return
openMap
(
name
,
-
1
);
}
/**
* Open a data map where reads are based on the specified version / savepoint.
*
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @param readVersion the version used for reading
* @return the transaction map
*/
public
<
K
,
V
>
TransactionMap
<
K
,
V
>
openMap
(
String
name
,
long
readVersion
)
{
checkOpen
();
return
new
TransactionMap
<
K
,
V
>(
this
,
name
,
new
ObjectDataType
(),
new
ObjectDataType
()
,
readVersion
);
new
ObjectDataType
());
}
/**
...
...
@@ -464,11 +433,10 @@ public class TransactionStore {
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @param readVersion the version used for reading
* @param builder the builder
* @return the transaction map
*/
public
<
K
,
V
>
TransactionMap
<
K
,
V
>
openMap
(
String
name
,
long
readVersion
,
Builder
<
K
,
V
>
builder
)
{
public
<
K
,
V
>
TransactionMap
<
K
,
V
>
openMap
(
String
name
,
Builder
<
K
,
V
>
builder
)
{
checkOpen
();
DataType
keyType
=
builder
.
getKeyType
();
if
(
keyType
==
null
)
{
...
...
@@ -478,7 +446,7 @@ public class TransactionStore {
if
(
valueType
==
null
)
{
valueType
=
new
ObjectDataType
();
}
return
new
TransactionMap
<
K
,
V
>(
this
,
name
,
keyType
,
valueType
,
readVersion
);
return
new
TransactionMap
<
K
,
V
>(
this
,
name
,
keyType
,
valueType
);
}
/**
...
...
@@ -556,34 +524,55 @@ public class TransactionStore {
* Key: key the key of the data.
* Value: { transactionId, oldVersion, value }
*/
private
final
MVMap
<
K
,
Object
[]>
map
Write
;
private
final
MVMap
<
K
,
Object
[]>
map
;
/**
* The map used for reading (possibly an older version). Reading is done
* on an older version so that changes are not immediately visible, to
* support statement processing (for example
* "update test set id = id + 1").
* <p>
* Key: key the key of the data.
* Value: { transactionId, oldVersion, value }
* If a record was read that was updated by this transaction, and the
* update occurred before this log id, the older version is read. This
* is so that changes are not immediately visible, to support statement
* processing (for example "update test set id = id + 1").
*/
private
final
MVMap
<
K
,
Object
[]>
mapRead
;
private
long
readLogId
=
Long
.
MAX_VALUE
;
TransactionMap
(
Transaction
transaction
,
String
name
,
DataType
keyType
,
DataType
valueType
,
long
readVersion
)
{
DataType
valueType
)
{
this
.
transaction
=
transaction
;
ArrayType
arrayType
=
new
ArrayType
(
new
DataType
[]
{
new
ObjectDataType
(),
new
ObjectDataType
(),
valueType
});
MVMap
.
Builder
<
K
,
Object
[]>
builder
=
new
MVMap
.
Builder
<
K
,
Object
[]>()
.
keyType
(
keyType
).
valueType
(
arrayType
);
mapWrite
=
transaction
.
store
.
store
.
openMap
(
name
,
builder
);
mapId
=
mapWrite
.
getId
();
if
(
readVersion
>=
0
)
{
mapRead
=
mapWrite
.
openVersion
(
readVersion
);
}
else
{
mapRead
=
mapWrite
;
}
map
=
transaction
.
store
.
store
.
openMap
(
name
,
builder
);
mapId
=
map
.
getId
();
}
private
TransactionMap
(
Transaction
transaction
,
MVMap
<
K
,
Object
[]>
map
,
int
mapId
)
{
this
.
transaction
=
transaction
;
this
.
map
=
map
;
this
.
mapId
=
mapId
;
}
/**
* Set the savepoint. Afterwards, reads are based on the specified
* savepoint.
*
* @param savepoint the savepoint
*/
public
void
setSavepoint
(
long
savepoint
)
{
this
.
readLogId
=
savepoint
;
}
/**
* Get a clone of this map for the given transaction.
*
* @param transaction the transaction
* @param savepoint the savepoint
* @return the map
*/
public
TransactionMap
<
K
,
V
>
getInstance
(
Transaction
transaction
,
long
savepoint
)
{
TransactionMap
<
K
,
V
>
m
=
new
TransactionMap
<
K
,
V
>(
transaction
,
map
,
mapId
);
m
.
setSavepoint
(
savepoint
);
return
m
;
}
/**
...
...
@@ -594,7 +583,7 @@ public class TransactionStore {
public
long
getSize
()
{
// TODO this method is very slow
long
size
=
0
;
Cursor
<
K
>
cursor
=
map
Read
.
keyIterator
(
null
);
Cursor
<
K
>
cursor
=
map
.
keyIterator
(
null
);
while
(
cursor
.
hasNext
())
{
K
key
=
cursor
.
next
();
if
(
get
(
key
)
!=
null
)
{
...
...
@@ -708,11 +697,10 @@ public class TransactionStore {
* @return true if the value was set
*/
public
boolean
trySet
(
K
key
,
V
value
,
boolean
onlyIfUnchanged
)
{
MVMap
<
K
,
Object
[]>
m
=
mapRead
;
Object
[]
current
=
mapWrite
.
get
(
key
);
Object
[]
current
=
map
.
get
(
key
);
if
(
onlyIfUnchanged
)
{
Object
[]
old
=
m
.
get
(
key
);
if
(!
map
Write
.
areValuesEqual
(
old
,
current
))
{
Object
[]
old
=
getArray
(
key
,
readLogId
);
if
(!
map
.
areValuesEqual
(
old
,
current
))
{
long
tx
=
(
Long
)
current
[
0
];
if
(
tx
==
transaction
.
transactionId
)
{
if
(
value
==
null
)
{
...
...
@@ -731,7 +719,6 @@ public class TransactionStore {
}
}
}
long
oldVersion
=
transaction
.
store
.
store
.
getCurrentVersion
()
-
1
;
int
opType
;
if
(
current
==
null
||
current
[
2
]
==
null
)
{
if
(
value
==
null
)
{
...
...
@@ -747,13 +734,14 @@ public class TransactionStore {
opType
=
Transaction
.
OP_SET
;
}
}
Object
[]
newValue
=
{
transaction
.
transactionId
,
oldVersion
,
value
};
Object
[]
newValue
=
{
transaction
.
transactionId
,
transaction
.
logId
,
value
};
if
(
current
==
null
)
{
// a new value
newValue
[
1
]
=
null
;
Object
[]
old
=
mapWrite
.
putIfAbsent
(
key
,
newValue
);
Object
[]
old
=
map
.
putIfAbsent
(
key
,
newValue
);
if
(
old
==
null
)
{
transaction
.
log
(
opType
,
mapId
,
key
);
transaction
.
log
(
opType
,
mapId
,
key
,
current
);
return
true
;
}
return
false
;
...
...
@@ -761,15 +749,8 @@ public class TransactionStore {
long
tx
=
(
Long
)
current
[
0
];
if
(
tx
==
transaction
.
transactionId
)
{
// added or updated by this transaction
if
(
mapWrite
.
replace
(
key
,
current
,
newValue
))
{
if
(
current
[
1
]
==
null
)
{
transaction
.
log
(
opType
,
mapId
,
key
);
}
else
{
long
c
=
(
Long
)
current
[
1
];
if
(
c
!=
oldVersion
)
{
transaction
.
log
(
opType
,
mapId
,
key
);
}
}
if
(
map
.
replace
(
key
,
current
,
newValue
))
{
transaction
.
log
(
opType
,
mapId
,
key
,
current
);
return
true
;
}
// strange, somebody overwrite the value
...
...
@@ -781,8 +762,8 @@ public class TransactionStore {
if
(!
open
)
{
// the transaction is committed:
// overwrite the value
if
(
map
Write
.
replace
(
key
,
current
,
newValue
))
{
transaction
.
log
(
opType
,
mapId
,
key
);
if
(
map
.
replace
(
key
,
current
,
newValue
))
{
transaction
.
log
(
opType
,
mapId
,
key
,
current
);
return
true
;
}
// somebody else was faster
...
...
@@ -799,7 +780,7 @@ public class TransactionStore {
* @return the value or null
*/
public
V
get
(
K
key
)
{
return
get
(
key
,
mapRea
d
);
return
get
(
key
,
readLogI
d
);
}
/**
...
...
@@ -809,7 +790,7 @@ public class TransactionStore {
* @return the value or null
*/
public
V
getLatest
(
K
key
)
{
return
get
(
key
,
mapWrite
);
return
get
(
key
,
Long
.
MAX_VALUE
);
}
/**
...
...
@@ -826,41 +807,46 @@ public class TransactionStore {
* Get the value for the given key.
*
* @param key the key
* @param m
the map
* @param m
axLogId the maximum log id
* @return the value or null
*/
@SuppressWarnings
(
"unchecked"
)
public
V
get
(
K
key
,
MVMap
<
K
,
Object
[]>
m
)
{
public
V
get
(
K
key
,
long
maxLogId
)
{
checkOpen
();
Object
[]
data
=
getArray
(
key
,
maxLogId
);
return
data
==
null
?
null
:
(
V
)
data
[
2
];
}
private
Object
[]
getArray
(
K
key
,
long
maxLog
)
{
Object
[]
data
=
map
.
get
(
key
);
while
(
true
)
{
Object
[]
data
=
m
.
get
(
key
);
long
tx
;
if
(
data
==
null
)
{
// doesn't exist or deleted by a committed transaction
return
null
;
}
tx
=
(
Long
)
data
[
0
];
long
logId
=
(
Long
)
data
[
1
];
if
(
tx
==
transaction
.
transactionId
)
{
// added by this transaction
return
(
V
)
data
[
2
];
if
(
logId
<
maxLog
)
{
return
data
;
}
}
// added or updated by another transaction
boolean
open
=
transaction
.
store
.
openTransactions
.
containsKey
(
tx
);
if
(!
open
)
{
// it is committed
return
(
V
)
data
[
2
]
;
return
data
;
}
tx
=
(
Long
)
data
[
0
];
// get the value before the uncommitted transaction
if
(
data
[
1
]
==
null
)
{
// a new entry
return
null
;
}
long
oldVersion
=
(
Long
)
data
[
1
];
m
=
mapWrite
.
openVersion
(
oldVersion
);
long
[]
x
=
new
long
[]
{
tx
,
logId
};
data
=
transaction
.
store
.
undoLog
.
get
(
x
);
data
=
(
Object
[])
data
[
3
];
}
}
/**
* Rename the map.
*
...
...
@@ -868,7 +854,7 @@ public class TransactionStore {
*/
public
void
renameMap
(
String
newMapName
)
{
// TODO rename maps transactionally
map
Write
.
renameMap
(
newMapName
);
map
.
renameMap
(
newMapName
);
}
/**
...
...
@@ -877,7 +863,7 @@ public class TransactionStore {
* @return true if closed
*/
public
boolean
isClosed
()
{
return
map
Write
.
isClosed
();
return
map
.
isClosed
();
}
/**
...
...
@@ -885,7 +871,7 @@ public class TransactionStore {
*/
public
void
removeMap
()
{
// TODO remove in a transaction
map
Write
.
removeMap
();
map
.
removeMap
();
}
/**
...
...
@@ -893,7 +879,7 @@ public class TransactionStore {
*/
public
void
clear
()
{
// TODO truncate transactionally
map
Write
.
clear
();
map
.
clear
();
}
/**
...
...
@@ -903,7 +889,7 @@ public class TransactionStore {
*/
public
K
firstKey
()
{
// TODO transactional firstKey
return
map
Read
.
firstKey
();
return
map
.
firstKey
();
}
/**
...
...
@@ -913,7 +899,7 @@ public class TransactionStore {
*/
public
K
lastKey
()
{
// TODO transactional lastKey
return
map
Read
.
lastKey
();
return
map
.
lastKey
();
}
/**
...
...
@@ -924,7 +910,7 @@ public class TransactionStore {
*/
public
Iterator
<
K
>
keyIterator
(
K
from
)
{
// TODO transactional keyIterator
return
map
Read
.
keyIterator
(
from
);
return
map
.
keyIterator
(
from
);
}
/**
...
...
@@ -935,7 +921,7 @@ public class TransactionStore {
*/
public
K
ceilingKey
(
K
key
)
{
// TODO transactional ceilingKey
return
map
Read
.
ceilingKey
(
key
);
return
map
.
ceilingKey
(
key
);
}
/**
...
...
@@ -947,7 +933,7 @@ public class TransactionStore {
*/
public
K
higherKey
(
K
key
)
{
// TODO transactional higherKey
return
map
Read
.
higherKey
(
key
);
return
map
.
higherKey
(
key
);
}
/**
...
...
@@ -959,7 +945,7 @@ public class TransactionStore {
*/
public
K
lowerKey
(
K
key
)
{
// TODO Auto-generated method stub
return
map
Read
.
lowerKey
(
key
);
return
map
.
lowerKey
(
key
);
}
public
Transaction
getTransaction
()
{
...
...
h2/src/main/org/h2/mvstore/db/TransactionStore2.java
deleted
100644 → 0
浏览文件 @
237b1a55
/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package
org
.
h2
.
mvstore
.
db
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.Iterator
;
import
java.util.List
;
import
java.util.Map
;
import
org.h2.mvstore.Cursor
;
import
org.h2.mvstore.DataUtils
;
import
org.h2.mvstore.MVMap
;
import
org.h2.mvstore.MVMapConcurrent
;
import
org.h2.mvstore.MVStore
;
import
org.h2.mvstore.MVMap.Builder
;
import
org.h2.mvstore.type.DataType
;
import
org.h2.mvstore.type.ObjectDataType
;
import
org.h2.util.New
;
/**
* A store that supports concurrent transactions.
*/
public
class
TransactionStore2
{
private
static
final
String
LAST_TRANSACTION_ID
=
"lastTransactionId"
;
/**
* The store.
*/
final
MVStore
store
;
/**
* The persisted map of open transaction.
* Key: transactionId, value: [ status, name ].
*/
final
MVMap
<
Long
,
Object
[]>
openTransactions
;
/**
* The map of open transaction objects.
* Key: transactionId, value: transaction object.
*/
final
HashMap
<
Long
,
Transaction
>
openTransactionMap
=
New
.
hashMap
();
/**
* The undo log.
* Key: [ transactionId, logId ], value: [ opType, mapId, key, oldValue ].
*/
final
MVMap
<
long
[],
Object
[]>
undoLog
;
/**
* The lock timeout in milliseconds. 0 means timeout immediately.
*/
long
lockTimeout
;
/**
* The transaction settings. "lastTransaction" the last transaction id.
*/
private
final
MVMap
<
String
,
String
>
settings
;
private
long
lastTransactionIdStored
;
private
long
lastTransactionId
;
/**
* Create a new transaction store.
*
* @param store the store
*/
public
TransactionStore2
(
MVStore
store
)
{
this
(
store
,
new
ObjectDataType
());
}
/**
* Create a new transaction store.
*
* @param store the store
* @param keyType the data type for map keys
*/
public
TransactionStore2
(
MVStore
store
,
DataType
keyType
)
{
this
.
store
=
store
;
settings
=
store
.
openMap
(
"settings"
);
openTransactions
=
store
.
openMap
(
"openTransactions"
,
new
MVMapConcurrent
.
Builder
<
Long
,
Object
[]>());
// commit could be faster if we have one undo log per transaction,
// or a range delete operation for maps
ArrayType
valueType
=
new
ArrayType
(
new
DataType
[]{
new
ObjectDataType
(),
new
ObjectDataType
(),
keyType
,
new
ObjectDataType
()
});
MVMapConcurrent
.
Builder
<
long
[],
Object
[]>
builder
=
new
MVMapConcurrent
.
Builder
<
long
[],
Object
[]>().
valueType
(
valueType
);
// TODO escape other map names, to avoid conflicts
undoLog
=
store
.
openMap
(
"undoLog"
,
builder
);
init
();
}
private
void
init
()
{
String
s
=
settings
.
get
(
LAST_TRANSACTION_ID
);
if
(
s
!=
null
)
{
lastTransactionId
=
Long
.
parseLong
(
s
);
lastTransactionIdStored
=
lastTransactionId
;
}
Long
lastKey
=
openTransactions
.
lastKey
();
if
(
lastKey
!=
null
&&
lastKey
.
longValue
()
>
lastTransactionId
)
{
throw
DataUtils
.
newIllegalStateException
(
"Last transaction not stored"
);
}
Cursor
<
Long
>
cursor
=
openTransactions
.
keyIterator
(
null
);
while
(
cursor
.
hasNext
())
{
long
id
=
cursor
.
next
();
Object
[]
data
=
openTransactions
.
get
(
id
);
int
status
=
(
Integer
)
data
[
0
];
String
name
=
(
String
)
data
[
1
];
long
[]
next
=
{
id
+
1
,
-
1
};
long
[]
last
=
undoLog
.
floorKey
(
next
);
if
(
last
==
null
)
{
// no entry
}
else
if
(
last
[
0
]
==
id
)
{
Transaction
t
=
new
Transaction
(
this
,
id
,
status
,
name
,
last
[
1
]);
openTransactionMap
.
put
(
id
,
t
);
}
}
}
/**
* Get the list of currently open transactions that have pending writes.
*
* @return the list of transactions
*/
public
synchronized
List
<
Transaction
>
getOpenTransactions
()
{
ArrayList
<
Transaction
>
list
=
New
.
arrayList
();
list
.
addAll
(
openTransactionMap
.
values
());
return
list
;
}
/**
* Close the transaction store.
*/
public
synchronized
void
close
()
{
// to avoid losing transaction ids
settings
.
put
(
LAST_TRANSACTION_ID
,
""
+
lastTransactionId
);
store
.
commit
();
}
/**
* Begin a new transaction.
*
* @return the transaction
*/
public
synchronized
Transaction
begin
()
{
long
transactionId
=
lastTransactionId
++;
int
status
=
Transaction
.
STATUS_OPEN
;
return
new
Transaction
(
this
,
transactionId
,
status
,
null
,
0
);
}
private
void
storeTransaction
(
Transaction
t
)
{
long
transactionId
=
t
.
getId
();
if
(
openTransactions
.
containsKey
(
transactionId
))
{
return
;
}
Object
[]
v
=
{
t
.
getStatus
(),
null
};
openTransactions
.
put
(
transactionId
,
v
);
openTransactionMap
.
put
(
transactionId
,
t
);
if
(
lastTransactionId
>
lastTransactionIdStored
)
{
lastTransactionIdStored
+=
32
;
settings
.
put
(
LAST_TRANSACTION_ID
,
""
+
lastTransactionIdStored
);
}
}
/**
* Prepare a transaction.
*
* @param transactionId the transaction id
*/
void
prepare
(
Transaction
t
)
{
storeTransaction
(
t
);
Object
[]
old
=
openTransactions
.
get
(
t
.
getId
());
Object
[]
v
=
{
Transaction
.
STATUS_PREPARED
,
old
[
1
]
};
openTransactions
.
put
(
t
.
getId
(),
v
);
store
.
commit
();
}
/**
* Log an entry.
*
* @param t the transaction
* @param logId the log id
* @param opType the operation type
* @param mapId the map id
* @param key the key
* @param oldValue the old value
*/
void
log
(
Transaction
t
,
long
logId
,
int
opType
,
int
mapId
,
Object
key
,
Object
oldValue
)
{
storeTransaction
(
t
);
long
[]
undoKey
=
{
t
.
getId
(),
logId
};
Object
[]
log
=
new
Object
[]
{
opType
,
mapId
,
key
,
oldValue
};
undoLog
.
put
(
undoKey
,
log
);
}
/**
* Set the name of a transaction.
*
* @param t the transaction
* @param name the new name
*/
void
setTransactionName
(
Transaction
t
,
String
name
)
{
storeTransaction
(
t
);
Object
[]
old
=
openTransactions
.
get
(
t
.
getId
());
Object
[]
v
=
{
old
[
0
],
name
};
openTransactions
.
put
(
t
.
getId
(),
v
);
}
/**
* Commit a transaction.
*
* @param t the transaction
* @param maxLogId the last log id
*/
void
commit
(
Transaction
t
,
long
maxLogId
)
{
for
(
long
logId
=
0
;
logId
<
maxLogId
;
logId
++)
{
long
[]
undoKey
=
new
long
[]
{
t
.
getId
(),
logId
};
Object
[]
op
=
undoLog
.
get
(
undoKey
);
int
opType
=
(
Integer
)
op
[
0
];
if
(
opType
==
Transaction
.
OP_REMOVE
)
{
int
mapId
=
(
Integer
)
op
[
1
];
Map
<
String
,
String
>
meta
=
store
.
getMetaMap
();
String
m
=
meta
.
get
(
"map."
+
mapId
);
String
mapName
=
DataUtils
.
parseMap
(
m
).
get
(
"name"
);
MVMap
<
Object
,
Object
[]>
map
=
store
.
openMap
(
mapName
);
Object
key
=
op
[
2
];
Object
[]
value
=
map
.
get
(
key
);
// possibly the entry was added later on
// so we have to check
if
(
value
[
2
]
==
null
)
{
// remove the value
map
.
remove
(
key
);
}
}
undoLog
.
remove
(
undoKey
);
}
openTransactions
.
remove
(
t
.
getId
());
openTransactionMap
.
remove
(
t
.
getId
());
}
/**
* Roll a transaction back.
*
* @param t the transaction
* @param maxLogId the last log id
*/
void
rollback
(
Transaction
t
,
long
maxLogId
)
{
rollbackTo
(
t
,
maxLogId
,
0
);
openTransactions
.
remove
(
t
.
getId
());
openTransactionMap
.
remove
(
t
.
getId
());
}
/**
* Rollback to an old savepoint.
*
* @param t the transaction
* @param maxLogId the last log id
* @param toLogId the log id to roll back to
*/
void
rollbackTo
(
Transaction
t
,
long
maxLogId
,
long
toLogId
)
{
for
(
long
logId
=
maxLogId
-
1
;
logId
>=
toLogId
;
logId
--)
{
Object
[]
op
=
undoLog
.
get
(
new
long
[]
{
t
.
getId
(),
logId
});
int
mapId
=
((
Integer
)
op
[
1
]).
intValue
();
Map
<
String
,
String
>
meta
=
store
.
getMetaMap
();
String
m
=
meta
.
get
(
"map."
+
mapId
);
String
mapName
=
DataUtils
.
parseMap
(
m
).
get
(
"name"
);
MVMap
<
Object
,
Object
[]>
map
=
store
.
openMap
(
mapName
);
Object
key
=
op
[
2
];
Object
[]
oldValue
=
(
Object
[])
op
[
3
];
if
(
oldValue
==
null
)
{
// this transaction added the value
map
.
remove
(
key
);
}
else
{
// this transaction updated the value
map
.
put
(
key
,
oldValue
);
}
undoLog
.
remove
(
op
);
}
}
/**
* A transaction.
*/
public
static
class
Transaction
{
/**
* The status of an open transaction.
*/
public
static
final
int
STATUS_OPEN
=
0
;
/**
* The status of a prepared transaction.
*/
public
static
final
int
STATUS_PREPARED
=
1
;
/**
* The status of a closed transaction (committed or rolled back).
*/
public
static
final
int
STATUS_CLOSED
=
2
;
/**
* The operation type for changes in a map.
*/
static
final
int
OP_REMOVE
=
0
,
OP_ADD
=
1
,
OP_SET
=
2
;
/**
* The transaction store.
*/
final
TransactionStore2
store
;
/**
* The version of the store at the time the transaction was started.
*/
final
long
startVersion
;
/**
* The transaction id.
*/
final
Long
transactionId
;
long
logId
;
private
int
status
;
private
String
name
;
Transaction
(
TransactionStore2
store
,
long
transactionId
,
int
status
,
String
name
,
long
logId
)
{
this
.
store
=
store
;
this
.
startVersion
=
store
.
store
.
getCurrentVersion
();
this
.
transactionId
=
transactionId
;
this
.
status
=
status
;
this
.
name
=
name
;
this
.
logId
=
logId
;
}
/**
* Get the transaction id.
*
* @return the transaction id
*/
public
Long
getId
()
{
return
transactionId
;
}
/**
* Get the transaction status.
*
* @return the status
*/
public
int
getStatus
()
{
return
status
;
}
/**
* Set the name of the transaction.
*
* @param name the new name
*/
public
void
setName
(
String
name
)
{
checkOpen
();
store
.
setTransactionName
(
this
,
name
);
this
.
name
=
name
;
}
/**
* Get the name of the transaction.
*
* @return name the name
*/
public
String
getName
()
{
return
name
;
}
/**
* Create a new savepoint.
*
* @return the savepoint id
*/
public
long
setSavepoint
()
{
checkOpen
();
return
logId
;
}
/**
* Add a log entry.
*
* @param opType the operation type
* @param mapId the map id
* @param key the key
* @param oldValue the old value
*/
void
log
(
int
opType
,
int
mapId
,
Object
key
,
Object
oldValue
)
{
store
.
log
(
this
,
logId
++,
opType
,
mapId
,
key
,
oldValue
);
}
/**
* Open a data map.
*
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @return the transaction map
*/
public
<
K
,
V
>
TransactionMap
<
K
,
V
>
openMap
(
String
name
)
{
checkOpen
();
return
new
TransactionMap
<
K
,
V
>(
this
,
name
,
new
ObjectDataType
(),
new
ObjectDataType
());
}
/**
* Open the map to store the data.
*
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @param builder the builder
* @return the transaction map
*/
public
<
K
,
V
>
TransactionMap
<
K
,
V
>
openMap
(
String
name
,
Builder
<
K
,
V
>
builder
)
{
checkOpen
();
DataType
keyType
=
builder
.
getKeyType
();
if
(
keyType
==
null
)
{
keyType
=
new
ObjectDataType
();
}
DataType
valueType
=
builder
.
getValueType
();
if
(
valueType
==
null
)
{
valueType
=
new
ObjectDataType
();
}
return
new
TransactionMap
<
K
,
V
>(
this
,
name
,
keyType
,
valueType
);
}
/**
* Roll back to the given savepoint. This is only allowed if the
* transaction is open.
*
* @param savepointId the savepoint id
*/
public
void
rollbackToSavepoint
(
long
savepointId
)
{
checkOpen
();
store
.
rollbackTo
(
this
,
this
.
logId
,
savepointId
);
this
.
logId
=
savepointId
;
}
/**
* Prepare the transaction. Afterwards, the transaction can only be
* committed or rolled back.
*/
public
void
prepare
()
{
checkOpen
();
store
.
prepare
(
this
);
status
=
STATUS_PREPARED
;
}
/**
* Commit the transaction. Afterwards, this transaction is closed.
*/
public
void
commit
()
{
if
(
status
!=
STATUS_CLOSED
)
{
store
.
commit
(
this
,
logId
);
status
=
STATUS_CLOSED
;
}
}
/**
* Roll the transaction back. Afterwards, this transaction is closed.
*/
public
void
rollback
()
{
if
(
status
!=
STATUS_CLOSED
)
{
store
.
rollback
(
this
,
logId
);
status
=
STATUS_CLOSED
;
}
}
/**
* Check whether this transaction is still open.
*/
void
checkOpen
()
{
if
(
status
!=
STATUS_OPEN
)
{
throw
DataUtils
.
newIllegalStateException
(
"Transaction is closed"
);
}
}
public
long
getCurrentVersion
()
{
return
store
.
store
.
getCurrentVersion
();
}
}
/**
* A map that supports transactions.
*
* @param <K> the key type
* @param <V> the value type
*/
public
static
class
TransactionMap
<
K
,
V
>
{
private
Transaction
transaction
;
private
final
int
mapId
;
/**
* The map used for writing (the latest version).
* <p>
* Key: key the key of the data.
* Value: { transactionId, oldVersion, value }
*/
private
final
MVMap
<
K
,
Object
[]>
map
;
/**
* If a record was read that was updated by this transaction, and the
* update occurred before this log id, the older version is read. This
* is so that changes are not immediately visible, to support statement
* processing (for example "update test set id = id + 1").
*/
private
long
readLogId
=
Long
.
MAX_VALUE
;
TransactionMap
(
Transaction
transaction
,
String
name
,
DataType
keyType
,
DataType
valueType
)
{
this
.
transaction
=
transaction
;
ArrayType
arrayType
=
new
ArrayType
(
new
DataType
[]
{
new
ObjectDataType
(),
new
ObjectDataType
(),
valueType
});
MVMap
.
Builder
<
K
,
Object
[]>
builder
=
new
MVMap
.
Builder
<
K
,
Object
[]>()
.
keyType
(
keyType
).
valueType
(
arrayType
);
map
=
transaction
.
store
.
store
.
openMap
(
name
,
builder
);
mapId
=
map
.
getId
();
}
private
TransactionMap
(
Transaction
transaction
,
MVMap
<
K
,
Object
[]>
map
,
int
mapId
)
{
this
.
transaction
=
transaction
;
this
.
map
=
map
;
this
.
mapId
=
mapId
;
}
/**
* Set the savepoint. Afterwards, reads are based on the specified
* savepoint.
*
* @param savepoint the savepoint
*/
public
void
setSavepoint
(
long
savepoint
)
{
this
.
readLogId
=
savepoint
;
}
/**
* Get a clone of this map for the given transaction.
*
* @param transaction the transaction
* @param savepoint the savepoint
* @return the map
*/
public
TransactionMap
<
K
,
V
>
getInstance
(
Transaction
transaction
,
long
savepoint
)
{
TransactionMap
<
K
,
V
>
m
=
new
TransactionMap
<
K
,
V
>(
transaction
,
map
,
mapId
);
m
.
setSavepoint
(
savepoint
);
return
m
;
}
/**
* Get the size of the map as seen by this transaction.
*
* @return the size
*/
public
long
getSize
()
{
// TODO this method is very slow
long
size
=
0
;
Cursor
<
K
>
cursor
=
map
.
keyIterator
(
null
);
while
(
cursor
.
hasNext
())
{
K
key
=
cursor
.
next
();
if
(
get
(
key
)
!=
null
)
{
size
++;
}
}
return
size
;
}
private
void
checkOpen
()
{
transaction
.
checkOpen
();
}
/**
* Remove an entry.
* <p>
* If the row is locked, this method will retry until the row could be
* updated or until a lock timeout.
*
* @param key the key
* @throws IllegalStateException if a lock timeout occurs
*/
public
V
remove
(
K
key
)
{
return
set
(
key
,
null
);
}
/**
* Update the value for the given key.
* <p>
* If the row is locked, this method will retry until the row could be
* updated or until a lock timeout.
*
* @param key the key
* @param value the new value (not null)
* @throws IllegalStateException if a lock timeout occurs
*/
public
V
put
(
K
key
,
V
value
)
{
DataUtils
.
checkArgument
(
value
!=
null
,
"The value may not be null"
);
return
set
(
key
,
value
);
}
private
V
set
(
K
key
,
V
value
)
{
checkOpen
();
long
start
=
0
;
while
(
true
)
{
V
old
=
get
(
key
);
boolean
ok
=
trySet
(
key
,
value
,
false
);
if
(
ok
)
{
return
old
;
}
// an uncommitted transaction:
// wait until it is committed, or until the lock timeout
long
timeout
=
transaction
.
store
.
lockTimeout
;
if
(
timeout
==
0
)
{
throw
DataUtils
.
newIllegalStateException
(
"Lock timeout"
);
}
if
(
start
==
0
)
{
start
=
System
.
currentTimeMillis
();
}
else
{
long
t
=
System
.
currentTimeMillis
()
-
start
;
if
(
t
>
timeout
)
{
throw
DataUtils
.
newIllegalStateException
(
"Lock timeout"
);
}
// TODO use wait/notify instead
try
{
Thread
.
sleep
(
1
);
}
catch
(
InterruptedException
e
)
{
// ignore
}
}
}
}
/**
* Try to remove the value for the given key.
* <p>
* This will fail if the row is locked by another transaction (that
* means, if another open transaction changed the row).
*
* @param key the key
* @return whether the entry could be removed
*/
public
boolean
tryRemove
(
K
key
)
{
return
trySet
(
key
,
null
,
false
);
}
/**
* Try to update the value for the given key.
* <p>
* This will fail if the row is locked by another transaction (that
* means, if another open transaction changed the row).
*
* @param key the key
* @param value the new value
* @return whether the entry could be updated
*/
public
boolean
tryPut
(
K
key
,
V
value
)
{
DataUtils
.
checkArgument
(
value
!=
null
,
"The value may not be null"
);
return
trySet
(
key
,
value
,
false
);
}
/**
* Try to set or remove the value. When updating only unchanged entries,
* then the value is only changed if it was not changed after opening
* the map.
*
* @param key the key
* @param value the new value (null to remove the value)
* @param onlyIfUnchanged only set the value if it was not changed (by
* this or another transaction) since the map was opened
* @return true if the value was set
*/
public
boolean
trySet
(
K
key
,
V
value
,
boolean
onlyIfUnchanged
)
{
Object
[]
current
=
map
.
get
(
key
);
if
(
onlyIfUnchanged
)
{
Object
[]
old
=
getArray
(
key
,
readLogId
);
if
(!
map
.
areValuesEqual
(
old
,
current
))
{
long
tx
=
(
Long
)
current
[
0
];
if
(
tx
==
transaction
.
transactionId
)
{
if
(
value
==
null
)
{
// ignore removing an entry
// if it was added or changed
// in the same statement
return
true
;
}
else
if
(
current
[
2
]
==
null
)
{
// add an entry that was removed
// in the same statement
}
else
{
return
false
;
}
}
else
{
return
false
;
}
}
}
int
opType
;
if
(
current
==
null
||
current
[
2
]
==
null
)
{
if
(
value
==
null
)
{
// remove a removed value
opType
=
Transaction
.
OP_SET
;
}
else
{
opType
=
Transaction
.
OP_ADD
;
}
}
else
{
if
(
value
==
null
)
{
opType
=
Transaction
.
OP_REMOVE
;
}
else
{
opType
=
Transaction
.
OP_SET
;
}
}
Object
[]
newValue
=
{
transaction
.
transactionId
,
transaction
.
logId
,
value
};
if
(
current
==
null
)
{
// a new value
Object
[]
old
=
map
.
putIfAbsent
(
key
,
newValue
);
if
(
old
==
null
)
{
transaction
.
log
(
opType
,
mapId
,
key
,
current
);
return
true
;
}
return
false
;
}
long
tx
=
(
Long
)
current
[
0
];
if
(
tx
==
transaction
.
transactionId
)
{
// added or updated by this transaction
if
(
map
.
replace
(
key
,
current
,
newValue
))
{
transaction
.
log
(
opType
,
mapId
,
key
,
current
);
return
true
;
}
// strange, somebody overwrite the value
// even thought the change was not committed
return
false
;
}
// added or updated by another transaction
boolean
open
=
transaction
.
store
.
openTransactions
.
containsKey
(
tx
);
if
(!
open
)
{
// the transaction is committed:
// overwrite the value
if
(
map
.
replace
(
key
,
current
,
newValue
))
{
transaction
.
log
(
opType
,
mapId
,
key
,
current
);
return
true
;
}
// somebody else was faster
return
false
;
}
// the transaction is not yet committed
return
false
;
}
/**
* Get the value for the given key at the time when this map was opened.
*
* @param key the key
* @return the value or null
*/
public
V
get
(
K
key
)
{
return
get
(
key
,
readLogId
);
}
/**
* Get the most recent value for the given key.
*
* @param key the key
* @return the value or null
*/
public
V
getLatest
(
K
key
)
{
return
get
(
key
,
Long
.
MAX_VALUE
);
}
/**
* Whether the map contains the key.
*
* @param key the key
* @return true if the map contains an entry for this key
*/
public
boolean
containsKey
(
K
key
)
{
return
get
(
key
)
!=
null
;
}
/**
* Get the value for the given key.
*
* @param key the key
* @param maxLogId the maximum log id
* @return the value or null
*/
@SuppressWarnings
(
"unchecked"
)
public
V
get
(
K
key
,
long
maxLogId
)
{
checkOpen
();
Object
[]
data
=
getArray
(
key
,
maxLogId
);
return
data
==
null
?
null
:
(
V
)
data
[
2
];
}
private
Object
[]
getArray
(
K
key
,
long
maxLog
)
{
Object
[]
data
=
map
.
get
(
key
);
while
(
true
)
{
long
tx
;
if
(
data
==
null
)
{
// doesn't exist or deleted by a committed transaction
return
null
;
}
tx
=
(
Long
)
data
[
0
];
long
logId
=
(
Long
)
data
[
1
];
if
(
tx
==
transaction
.
transactionId
)
{
// added by this transaction
if
(
logId
<
maxLog
)
{
return
data
;
}
}
// added or updated by another transaction
boolean
open
=
transaction
.
store
.
openTransactions
.
containsKey
(
tx
);
if
(!
open
)
{
// it is committed
return
data
;
}
// get the value before the uncommitted transaction
long
[]
x
=
new
long
[]
{
tx
,
logId
};
data
=
transaction
.
store
.
undoLog
.
get
(
x
);
data
=
(
Object
[])
data
[
3
];
}
}
/**
* Rename the map.
*
* @param newMapName the new map name
*/
public
void
renameMap
(
String
newMapName
)
{
// TODO rename maps transactionally
map
.
renameMap
(
newMapName
);
}
/**
* Check whether this map is closed.
*
* @return true if closed
*/
public
boolean
isClosed
()
{
return
map
.
isClosed
();
}
/**
* Remove the map.
*/
public
void
removeMap
()
{
// TODO remove in a transaction
map
.
removeMap
();
}
/**
* Clear the map.
*/
public
void
clear
()
{
// TODO truncate transactionally
map
.
clear
();
}
/**
* Get the first key.
*
* @return the first key, or null if empty
*/
public
K
firstKey
()
{
// TODO transactional firstKey
return
map
.
firstKey
();
}
/**
* Get the last key.
*
* @return the last key, or null if empty
*/
public
K
lastKey
()
{
// TODO transactional lastKey
return
map
.
lastKey
();
}
/**
* Iterate over all keys.
*
* @param from the first key to return
* @return the iterator
*/
public
Iterator
<
K
>
keyIterator
(
K
from
)
{
// TODO transactional keyIterator
return
map
.
keyIterator
(
from
);
}
/**
* Get the smallest key that is larger or equal to this key.
*
* @param key the key (may not be null)
* @return the result
*/
public
K
ceilingKey
(
K
key
)
{
// TODO transactional ceilingKey
return
map
.
ceilingKey
(
key
);
}
/**
* Get the smallest key that is larger than the given key, or null if no
* such key exists.
*
* @param key the key (may not be null)
* @return the result
*/
public
K
higherKey
(
K
key
)
{
// TODO transactional higherKey
return
map
.
higherKey
(
key
);
}
/**
* Get the largest key that is smaller than the given key, or null if no
* such key exists.
*
* @param key the key (may not be null)
* @return the result
*/
public
K
lowerKey
(
K
key
)
{
// TODO Auto-generated method stub
return
map
.
lowerKey
(
key
);
}
public
Transaction
getTransaction
()
{
return
transaction
;
}
}
/**
* A data type that contains an array of objects with the specified data
* types.
*/
public
static
class
ArrayType
implements
DataType
{
private
final
int
arrayLength
;
private
final
DataType
[]
elementTypes
;
ArrayType
(
DataType
[]
elementTypes
)
{
this
.
arrayLength
=
elementTypes
.
length
;
this
.
elementTypes
=
elementTypes
;
}
@Override
public
int
getMemory
(
Object
obj
)
{
Object
[]
array
=
(
Object
[])
obj
;
int
size
=
0
;
for
(
int
i
=
0
;
i
<
arrayLength
;
i
++)
{
DataType
t
=
elementTypes
[
i
];
size
+=
t
.
getMemory
(
array
[
i
]);
}
return
size
;
}
@Override
public
int
compare
(
Object
aObj
,
Object
bObj
)
{
if
(
aObj
==
bObj
)
{
return
0
;
}
Object
[]
a
=
(
Object
[])
aObj
;
Object
[]
b
=
(
Object
[])
bObj
;
for
(
int
i
=
0
;
i
<
arrayLength
;
i
++)
{
DataType
t
=
elementTypes
[
i
];
int
comp
=
t
.
compare
(
a
[
i
],
b
[
i
]);
if
(
comp
!=
0
)
{
return
comp
;
}
}
return
0
;
}
@Override
public
ByteBuffer
write
(
ByteBuffer
buff
,
Object
obj
)
{
Object
[]
array
=
(
Object
[])
obj
;
for
(
int
i
=
0
;
i
<
arrayLength
;
i
++)
{
DataType
t
=
elementTypes
[
i
];
t
.
write
(
buff
,
array
[
i
]);
}
return
buff
;
}
@Override
public
Object
read
(
ByteBuffer
buff
)
{
Object
[]
array
=
new
Object
[
arrayLength
];
for
(
int
i
=
0
;
i
<
arrayLength
;
i
++)
{
DataType
t
=
elementTypes
[
i
];
array
[
i
]
=
t
.
read
(
buff
);
}
return
array
;
}
}
}
h2/src/test/org/h2/test/store/TestMVTableEngine.java
浏览文件 @
de64ab1a
...
...
@@ -39,22 +39,24 @@ public class TestMVTableEngine extends TestBase {
testEncryption
();
testReadOnly
();
testReuseDiskSpace
();
//
testDataTypes();
testDataTypes
();
testLocking
();
//
testSimple();
testSimple
();
}
private
void
testSpeed
()
throws
Exception
{
String
dbName
;
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
dbName
=
"mvstore"
;
// dbName += ";LOCK_MODE=0";
dbName
+=
";LOCK_MODE=0"
;
// dbName += ";LOG=0";
testSpeed
(
dbName
);
int
tes
;
//Profiler prof = new Profiler().startCollecting();
dbName
=
"mvstore"
+
";DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine"
;
// dbName += ";LOCK_MODE=0";
dbName
+=
";LOCK_MODE=0"
;
// dbName += ";LOG=0";
testSpeed
(
dbName
);
//System.out.println(prof.getTop(10));
}
...
...
@@ -70,17 +72,51 @@ int tes;
String
password
=
getPassword
();
conn
=
DriverManager
.
getConnection
(
url
,
user
,
password
);
stat
=
conn
.
createStatement
();
stat
.
execute
(
"create table test(id int primary key, name varchar(255))"
);
long
time
=
System
.
currentTimeMillis
();
// stat.execute(
// "create table test(id int primary key, name varchar(255))" +
// "as select x, 'Hello World' from system_range(1, 200000)");
stat
.
execute
(
"create table test(id int primary key, name varchar)"
);
PreparedStatement
prep
=
conn
.
prepareStatement
(
"insert into test values(?, ?)"
);
prep
.
setString
(
2
,
"Hello World xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
);
long
time
=
System
.
currentTimeMillis
();
for
(
int
i
=
0
;
i
<
200000
;
i
++)
{
// -mx4g
// 10000 / 8000
// 1229 mvstore;LOCK_MODE=0
// 1455 mvstore;LOCK_MODE=0
// 19 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 659 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 1000 / 80000
// 1383 mvstore;LOCK_MODE=0
// 1618 mvstore;LOCK_MODE=0
// 244 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 965 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 100 / 800000
// 2061 mvstore;LOCK_MODE=0
// 2281 mvstore;LOCK_MODE=0
// 1414 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 2647 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 10 / 8000000
// 11453 mvstore;LOCK_MODE=0
// 11720 mvstore;LOCK_MODE=0
// 13605 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
// 25172 mvstore;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine;LOCK_MODE=0
prep
.
setString
(
2
,
new
String
(
new
char
[
10
]).
replace
((
char
)
0
,
'x'
));
for
(
int
i
=
0
;
i
<
8000000
;
i
++)
{
prep
.
setInt
(
1
,
i
);
prep
.
execute
();
}
System
.
out
.
println
((
System
.
currentTimeMillis
()
-
time
)
+
" "
+
dbName
);
//Profiler prof = new Profiler().startCollecting();
conn
.
close
();
//System.out.println(prof.getTop(10));
System
.
out
.
println
((
System
.
currentTimeMillis
()
-
time
)
+
" "
+
dbName
);
}
private
void
testEncryption
()
throws
Exception
{
...
...
h2/src/test/org/h2/test/store/TestTransactionStore.java
浏览文件 @
de64ab1a
...
...
@@ -16,9 +16,9 @@ import java.util.List;
import
java.util.Random
;
import
org.h2.mvstore.MVStore
;
import
org.h2.mvstore.db.TransactionStore
2
;
import
org.h2.mvstore.db.TransactionStore
2
.Transaction
;
import
org.h2.mvstore.db.TransactionStore
2
.TransactionMap
;
import
org.h2.mvstore.db.TransactionStore
;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.mvstore.db.TransactionStore.TransactionMap
;
import
org.h2.store.fs.FileUtils
;
import
org.h2.test.TestBase
;
import
org.h2.util.New
;
...
...
@@ -60,7 +60,7 @@ public class TestTransactionStore extends TestBase {
*/
private
void
testMultiStatement
()
{
MVStore
s
=
MVStore
.
open
(
null
);
TransactionStore
2
ts
=
new
TransactionStore2
(
s
);
TransactionStore
ts
=
new
TransactionStore
(
s
);
Transaction
tx
;
TransactionMap
<
String
,
String
>
m
;
long
startUpdate
;
...
...
@@ -141,14 +141,14 @@ public class TestTransactionStore extends TestBase {
FileUtils
.
delete
(
fileName
);
MVStore
s
;
TransactionStore
2
ts
;
TransactionStore
ts
;
Transaction
tx
;
Transaction
txOld
;
TransactionMap
<
String
,
String
>
m
;
List
<
Transaction
>
list
;
s
=
MVStore
.
open
(
fileName
);
ts
=
new
TransactionStore
2
(
s
);
ts
=
new
TransactionStore
(
s
);
tx
=
ts
.
begin
();
assertEquals
(
null
,
tx
.
getName
());
tx
.
setName
(
"first transaction"
);
...
...
@@ -166,7 +166,7 @@ public class TestTransactionStore extends TestBase {
s
.
close
();
s
=
MVStore
.
open
(
fileName
);
ts
=
new
TransactionStore
2
(
s
);
ts
=
new
TransactionStore
(
s
);
tx
=
ts
.
begin
();
assertEquals
(
1
,
tx
.
getId
());
m
=
tx
.
openMap
(
"test"
);
...
...
@@ -184,7 +184,7 @@ public class TestTransactionStore extends TestBase {
s
.
close
();
s
=
MVStore
.
open
(
fileName
);
ts
=
new
TransactionStore
2
(
s
);
ts
=
new
TransactionStore
(
s
);
tx
=
ts
.
begin
();
m
=
tx
.
openMap
(
"test"
);
// TransactionStore was not closed, so we lost some ids
...
...
@@ -209,7 +209,7 @@ public class TestTransactionStore extends TestBase {
private
void
testSavepoint
()
throws
Exception
{
MVStore
s
=
MVStore
.
open
(
null
);
TransactionStore
2
ts
=
new
TransactionStore2
(
s
);
TransactionStore
ts
=
new
TransactionStore
(
s
);
Transaction
tx
;
TransactionMap
<
String
,
String
>
m
;
...
...
@@ -262,7 +262,7 @@ public class TestTransactionStore extends TestBase {
"create table test(id int primary key, name varchar(255))"
);
MVStore
s
=
MVStore
.
open
(
null
);
TransactionStore
2
ts
=
new
TransactionStore2
(
s
);
TransactionStore
ts
=
new
TransactionStore
(
s
);
for
(
int
i
=
0
;
i
<
connectionCount
;
i
++)
{
Statement
stat
=
statements
.
get
(
i
);
// 100 ms to avoid blocking (the test is single threaded)
...
...
@@ -395,7 +395,7 @@ public class TestTransactionStore extends TestBase {
private
void
testConcurrentTransactionsReadCommitted
()
{
MVStore
s
=
MVStore
.
open
(
null
);
TransactionStore
2
ts
=
new
TransactionStore2
(
s
);
TransactionStore
ts
=
new
TransactionStore
(
s
);
Transaction
tx1
,
tx2
;
TransactionMap
<
String
,
String
>
m1
,
m2
;
...
...
@@ -467,7 +467,7 @@ public class TestTransactionStore extends TestBase {
private
void
testSingleConnection
()
{
MVStore
s
=
MVStore
.
open
(
null
);
TransactionStore
2
ts
=
new
TransactionStore2
(
s
);
TransactionStore
ts
=
new
TransactionStore
(
s
);
Transaction
tx
;
TransactionMap
<
String
,
String
>
m
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论