提交 de64ab1a authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore table engine (WIP)

上级 237b1a55
...@@ -23,8 +23,8 @@ import org.h2.jdbc.JdbcConnection; ...@@ -23,8 +23,8 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.mvstore.db.TransactionStore2; import org.h2.mvstore.db.TransactionStore;
import org.h2.mvstore.db.TransactionStore2.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.schema.Schema; import org.h2.schema.Schema;
...@@ -1257,7 +1257,7 @@ public class Session extends SessionWithState { ...@@ -1257,7 +1257,7 @@ public class Session extends SessionWithState {
* @param store the store * @param store the store
* @return the transaction * @return the transaction
*/ */
public Transaction getTransaction(TransactionStore2 store) { public Transaction getTransaction(TransactionStore store) {
if (transaction == null) { if (transaction == null) {
transaction = store.begin(); transaction = store.begin();
startStatement = -1; startStatement = -1;
......
...@@ -76,6 +76,11 @@ public class DataUtils { ...@@ -76,6 +76,11 @@ public class DataUtils {
*/ */
private static final byte[] EMPTY_BYTES = {}; 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. * Get the length of the variable size int.
* *
...@@ -739,8 +744,7 @@ public class DataUtils { ...@@ -739,8 +744,7 @@ public class DataUtils {
private static ByteBuffer grow(ByteBuffer buff, int len) { private static ByteBuffer grow(ByteBuffer buff, int len) {
len = buff.remaining() + len; len = buff.remaining() + len;
int capacity = buff.capacity(); int capacity = buff.capacity();
// grow at most 1 MB at a time len = Math.max(len, Math.min(capacity + MAX_GROW, capacity * 2));
len = Math.max(len, Math.min(capacity + 1024 * 1024, capacity * 2));
ByteBuffer temp = ByteBuffer.allocate(len); ByteBuffer temp = ByteBuffer.allocate(len);
buff.flip(); buff.flip();
temp.put(buff); temp.put(buff);
......
...@@ -35,9 +35,7 @@ public class MVDelegateIndex extends BaseIndex { ...@@ -35,9 +35,7 @@ public class MVDelegateIndex extends BaseIndex {
} }
} }
static int count;
public void add(Session session, Row row) { public void add(Session session, Row row) {
count++;
// nothing to do // nothing to do
} }
......
...@@ -18,8 +18,8 @@ import org.h2.index.Cursor; ...@@ -18,8 +18,8 @@ import org.h2.index.Cursor;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.db.TransactionStore2.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore2.TransactionMap; import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
...@@ -59,7 +59,6 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -59,7 +59,6 @@ public class MVPrimaryIndex extends BaseIndex {
valueType(valueType); valueType(valueType);
dataMap = mvTable.getTransaction(null).openMap(mapName, mapBuilder); dataMap = mvTable.getTransaction(null).openMap(mapName, mapBuilder);
Value k = dataMap.lastKey(); Value k = dataMap.lastKey();
dataMap.getTransaction().commit();
lastKey = k == null ? 0 : k.getLong(); lastKey = k == null ? 0 : k.getLong();
} }
...@@ -73,7 +72,6 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -73,7 +72,6 @@ public class MVPrimaryIndex extends BaseIndex {
rename(newName + "_DATA"); rename(newName + "_DATA");
String newMapName = newName + "_DATA_" + getId(); String newMapName = newName + "_DATA_" + getId();
map.renameMap(newMapName); map.renameMap(newMapName);
map.getTransaction().commit();
mapName = newMapName; mapName = newMapName;
} }
...@@ -233,10 +231,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -233,10 +231,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
TransactionMap<Value, Value> map = getMap(null); return getMap(null).getSize();
long size = map.getSize();
map.getTransaction().commit();
return size;
} }
public long getDiskSpaceUsed() { public long getDiskSpaceUsed() {
......
...@@ -17,8 +17,8 @@ import org.h2.index.Cursor; ...@@ -17,8 +17,8 @@ import org.h2.index.Cursor;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.db.TransactionStore2.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore2.TransactionMap; import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
...@@ -87,7 +87,6 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -87,7 +87,6 @@ public class MVSecondaryIndex extends BaseIndex {
TransactionMap<Value, Value> map = getMap(null); TransactionMap<Value, Value> map = getMap(null);
String newMapName = newName + "_" + getId(); String newMapName = newName + "_" + getId();
map.renameMap(newMapName); map.renameMap(newMapName);
map.getTransaction().commit();
mapName = newMapName; mapName = newMapName;
super.rename(newName); super.rename(newName);
} }
...@@ -218,10 +217,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -218,10 +217,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
TransactionMap<Value, Value> map = getMap(null); return getMap(null).getSize() == 0;
boolean result = map.getSize() == 0;
map.getTransaction().commit();
return result;
} }
@Override @Override
...@@ -232,10 +228,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -232,10 +228,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
TransactionMap<Value, Value> map = getMap(null); return getMap(null).getSize();
long size = map.getSize();
map.getTransaction().commit();
return size;
} }
public long getDiskSpaceUsed() { public long getDiskSpaceUsed() {
......
...@@ -28,7 +28,7 @@ import org.h2.index.IndexType; ...@@ -28,7 +28,7 @@ import org.h2.index.IndexType;
import org.h2.index.MultiVersionIndex; import org.h2.index.MultiVersionIndex;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.mvstore.db.TransactionStore2.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.schema.SchemaObject; import org.h2.schema.SchemaObject;
...@@ -48,7 +48,7 @@ import org.h2.value.Value; ...@@ -48,7 +48,7 @@ import org.h2.value.Value;
public class MVTable extends TableBase { public class MVTable extends TableBase {
private final String storeName; private final String storeName;
private final TransactionStore2 store; private final TransactionStore store;
private MVPrimaryIndex primaryIndex; private MVPrimaryIndex primaryIndex;
private ArrayList<Index> indexes = New.arrayList(); private ArrayList<Index> indexes = New.arrayList();
private long lastModificationId; private long lastModificationId;
...@@ -68,7 +68,7 @@ public class MVTable extends TableBase { ...@@ -68,7 +68,7 @@ public class MVTable extends TableBase {
*/ */
private boolean waitForLock; private boolean waitForLock;
public MVTable(CreateTableData data, String storeName, TransactionStore2 store) { public MVTable(CreateTableData data, String storeName, TransactionStore store) {
super(data); super(data);
nextAnalyze = database.getSettings().analyzeAuto; nextAnalyze = database.getSettings().analyzeAuto;
this.storeName = storeName; this.storeName = storeName;
...@@ -485,26 +485,16 @@ public class MVTable extends TableBase { ...@@ -485,26 +485,16 @@ public class MVTable extends TableBase {
@Override @Override
public void removeRow(Session session, Row row) { public void removeRow(Session session, Row row) {
lastModificationId = database.getNextModificationDataId(); lastModificationId = database.getNextModificationDataId();
int i = indexes.size() - 1; Transaction t = getTransaction(session);
long savepoint = t.setSavepoint();
try { try {
for (; i >= 0; i--) { for (int i = indexes.size() - 1; i >= 0; i--) {
Index index = indexes.get(i); Index index = indexes.get(i);
index.remove(session, row); index.remove(session, row);
} }
rowCount--; rowCount--;
} catch (Throwable e) { } catch (Throwable e) {
try { t.rollbackToSavepoint(savepoint);
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;
}
throw DbException.convert(e); throw DbException.convert(e);
} }
analyzeIfRequired(session); analyzeIfRequired(session);
...@@ -524,26 +514,16 @@ public class MVTable extends TableBase { ...@@ -524,26 +514,16 @@ public class MVTable extends TableBase {
@Override @Override
public void addRow(Session session, Row row) { public void addRow(Session session, Row row) {
lastModificationId = database.getNextModificationDataId(); lastModificationId = database.getNextModificationDataId();
int i = 0; Transaction t = getTransaction(session);
long savepoint = t.setSavepoint();
try { 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 index = indexes.get(i);
index.add(session, row); index.add(session, row);
} }
rowCount++; rowCount++;
} catch (Throwable e) { } catch (Throwable e) {
try { t.rollbackToSavepoint(savepoint);
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;
}
DbException de = DbException.convert(e); DbException de = DbException.convert(e);
if (de.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) { if (de.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) {
for (int j = 0; j < indexes.size(); j++) { for (int j = 0; j < indexes.size(); j++) {
...@@ -572,7 +552,7 @@ public class MVTable extends TableBase { ...@@ -572,7 +552,7 @@ public class MVTable extends TableBase {
} }
int rows = session.getDatabase().getSettings().analyzeSample; int rows = session.getDatabase().getSettings().analyzeSample;
int test; int test;
// Analyze.analyzeTable(session, this, rows, false); // Analyze.analyzeTable(session, this, rows, false);
} }
@Override @Override
...@@ -687,7 +667,7 @@ int test; ...@@ -687,7 +667,7 @@ int test;
return session.getTransaction(store); return session.getTransaction(store);
} }
public TransactionStore2 getStore() { public TransactionStore getStore() {
return store; return store;
} }
......
...@@ -42,7 +42,6 @@ public class MVTableEngine implements TableEngine { ...@@ -42,7 +42,6 @@ public class MVTableEngine implements TableEngine {
if (store == null) { if (store == null) {
return; return;
} }
// TODO this stores uncommitted transactions as well
store(store.getStore()); store(store.getStore());
} }
} }
...@@ -115,8 +114,11 @@ public class MVTableEngine implements TableEngine { ...@@ -115,8 +114,11 @@ public class MVTableEngine implements TableEngine {
* @param store the store * @param store the store
*/ */
static void store(MVStore store) { static void store(MVStore store) {
store.compact(50); if (!store.isReadOnly()) {
store.store(); store.commit();
store.compact(50);
store.store();
}
} }
/** /**
...@@ -142,12 +144,12 @@ public class MVTableEngine implements TableEngine { ...@@ -142,12 +144,12 @@ public class MVTableEngine implements TableEngine {
/** /**
* The transaction store. * The transaction store.
*/ */
private final TransactionStore2 transactionStore; private final TransactionStore transactionStore;
public Store(Database db, MVStore store) { public Store(Database db, MVStore store) {
this.db = db; this.db = db;
this.store = store; this.store = store;
this.transactionStore = new TransactionStore2(store, this.transactionStore = new TransactionStore(store,
new ValueDataType(null, null, null)); new ValueDataType(null, null, null));
} }
...@@ -155,7 +157,7 @@ public class MVTableEngine implements TableEngine { ...@@ -155,7 +157,7 @@ public class MVTableEngine implements TableEngine {
return store; return store;
} }
public TransactionStore2 getTransactionStore() { public TransactionStore getTransactionStore() {
return transactionStore; return transactionStore;
} }
......
...@@ -39,22 +39,24 @@ public class TestMVTableEngine extends TestBase { ...@@ -39,22 +39,24 @@ public class TestMVTableEngine extends TestBase {
testEncryption(); testEncryption();
testReadOnly(); testReadOnly();
testReuseDiskSpace(); testReuseDiskSpace();
// testDataTypes(); testDataTypes();
testLocking(); testLocking();
// testSimple(); testSimple();
} }
private void testSpeed() throws Exception { private void testSpeed() throws Exception {
String dbName; String dbName;
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
dbName = "mvstore"; dbName = "mvstore";
// dbName += ";LOCK_MODE=0"; dbName += ";LOCK_MODE=0";
// dbName += ";LOG=0";
testSpeed(dbName); testSpeed(dbName);
int tes; int tes;
//Profiler prof = new Profiler().startCollecting(); //Profiler prof = new Profiler().startCollecting();
dbName = "mvstore" + dbName = "mvstore" +
";DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine"; ";DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine";
// dbName += ";LOCK_MODE=0"; dbName += ";LOCK_MODE=0";
// dbName += ";LOG=0";
testSpeed(dbName); testSpeed(dbName);
//System.out.println(prof.getTop(10)); //System.out.println(prof.getTop(10));
} }
...@@ -70,17 +72,51 @@ int tes; ...@@ -70,17 +72,51 @@ int tes;
String password = getPassword(); String password = getPassword();
conn = DriverManager.getConnection(url, user, password); conn = DriverManager.getConnection(url, user, password);
stat = conn.createStatement(); 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 PreparedStatement prep = conn
.prepareStatement("insert into test values(?, ?)"); .prepareStatement("insert into test values(?, ?)");
prep.setString(2, "Hello World xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
long time = System.currentTimeMillis(); // -mx4g
for (int i = 0; i < 200000; i++) {
// 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.setInt(1, i);
prep.execute(); prep.execute();
} }
System.out.println((System.currentTimeMillis() - time) + " " + dbName); System.out.println((System.currentTimeMillis() - time) + " " + dbName);
//Profiler prof = new Profiler().startCollecting();
conn.close(); conn.close();
//System.out.println(prof.getTop(10));
System.out.println((System.currentTimeMillis() - time) + " " + dbName);
} }
private void testEncryption() throws Exception { private void testEncryption() throws Exception {
......
...@@ -16,9 +16,9 @@ import java.util.List; ...@@ -16,9 +16,9 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.TransactionStore2; import org.h2.mvstore.db.TransactionStore;
import org.h2.mvstore.db.TransactionStore2.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore2.TransactionMap; import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.New; import org.h2.util.New;
...@@ -60,7 +60,7 @@ public class TestTransactionStore extends TestBase { ...@@ -60,7 +60,7 @@ public class TestTransactionStore extends TestBase {
*/ */
private void testMultiStatement() { private void testMultiStatement() {
MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
TransactionStore2 ts = new TransactionStore2(s); TransactionStore ts = new TransactionStore(s);
Transaction tx; Transaction tx;
TransactionMap<String, String> m; TransactionMap<String, String> m;
long startUpdate; long startUpdate;
...@@ -141,14 +141,14 @@ public class TestTransactionStore extends TestBase { ...@@ -141,14 +141,14 @@ public class TestTransactionStore extends TestBase {
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s; MVStore s;
TransactionStore2 ts; TransactionStore ts;
Transaction tx; Transaction tx;
Transaction txOld; Transaction txOld;
TransactionMap<String, String> m; TransactionMap<String, String> m;
List<Transaction> list; List<Transaction> list;
s = MVStore.open(fileName); s = MVStore.open(fileName);
ts = new TransactionStore2(s); ts = new TransactionStore(s);
tx = ts.begin(); tx = ts.begin();
assertEquals(null, tx.getName()); assertEquals(null, tx.getName());
tx.setName("first transaction"); tx.setName("first transaction");
...@@ -166,7 +166,7 @@ public class TestTransactionStore extends TestBase { ...@@ -166,7 +166,7 @@ public class TestTransactionStore extends TestBase {
s.close(); s.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
ts = new TransactionStore2(s); ts = new TransactionStore(s);
tx = ts.begin(); tx = ts.begin();
assertEquals(1, tx.getId()); assertEquals(1, tx.getId());
m = tx.openMap("test"); m = tx.openMap("test");
...@@ -184,7 +184,7 @@ public class TestTransactionStore extends TestBase { ...@@ -184,7 +184,7 @@ public class TestTransactionStore extends TestBase {
s.close(); s.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
ts = new TransactionStore2(s); ts = new TransactionStore(s);
tx = ts.begin(); tx = ts.begin();
m = tx.openMap("test"); m = tx.openMap("test");
// TransactionStore was not closed, so we lost some ids // TransactionStore was not closed, so we lost some ids
...@@ -209,7 +209,7 @@ public class TestTransactionStore extends TestBase { ...@@ -209,7 +209,7 @@ public class TestTransactionStore extends TestBase {
private void testSavepoint() throws Exception { private void testSavepoint() throws Exception {
MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
TransactionStore2 ts = new TransactionStore2(s); TransactionStore ts = new TransactionStore(s);
Transaction tx; Transaction tx;
TransactionMap<String, String> m; TransactionMap<String, String> m;
...@@ -262,7 +262,7 @@ public class TestTransactionStore extends TestBase { ...@@ -262,7 +262,7 @@ public class TestTransactionStore extends TestBase {
"create table test(id int primary key, name varchar(255))"); "create table test(id int primary key, name varchar(255))");
MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
TransactionStore2 ts = new TransactionStore2(s); TransactionStore ts = new TransactionStore(s);
for (int i = 0; i < connectionCount; i++) { for (int i = 0; i < connectionCount; i++) {
Statement stat = statements.get(i); Statement stat = statements.get(i);
// 100 ms to avoid blocking (the test is single threaded) // 100 ms to avoid blocking (the test is single threaded)
...@@ -395,7 +395,7 @@ public class TestTransactionStore extends TestBase { ...@@ -395,7 +395,7 @@ public class TestTransactionStore extends TestBase {
private void testConcurrentTransactionsReadCommitted() { private void testConcurrentTransactionsReadCommitted() {
MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
TransactionStore2 ts = new TransactionStore2(s); TransactionStore ts = new TransactionStore(s);
Transaction tx1, tx2; Transaction tx1, tx2;
TransactionMap<String, String> m1, m2; TransactionMap<String, String> m1, m2;
...@@ -467,7 +467,7 @@ public class TestTransactionStore extends TestBase { ...@@ -467,7 +467,7 @@ public class TestTransactionStore extends TestBase {
private void testSingleConnection() { private void testSingleConnection() {
MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
TransactionStore2 ts = new TransactionStore2(s); TransactionStore ts = new TransactionStore(s);
Transaction tx; Transaction tx;
TransactionMap<String, String> m; TransactionMap<String, String> m;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论