提交 9d0f0920 authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 a329dec6
...@@ -31,6 +31,8 @@ Advanced Topics ...@@ -31,6 +31,8 @@ Advanced Topics
ODBC Driver</a><br /> ODBC Driver</a><br />
<a href="#acid"> <a href="#acid">
ACID</a><br /> ACID</a><br />
<a href="#durability">
Durability</a><br />
<a href="#using_recover_tool"> <a href="#using_recover_tool">
Using the Recover Tool</a><br /> Using the Recover Tool</a><br />
<a href="#file_locking_protocols"> <a href="#file_locking_protocols">
...@@ -327,12 +329,10 @@ To uninstall the ODBC driver, double click on h2odbcUninstall.exe. This will uni ...@@ -327,12 +329,10 @@ To uninstall the ODBC driver, double click on h2odbcUninstall.exe. This will uni
In the DBMS world, ACID stands for Atomicity, Consistency, Isolation, and Durability. In the DBMS world, ACID stands for Atomicity, Consistency, Isolation, and Durability.
<ul> <ul>
<li>Atomicity: Transactions must be atomic, that means either all tasks of a transaction are performed, or none. <li>Atomicity: Transactions must be atomic, that means either all tasks of a transaction are performed, or none.
</li><li>Consistency: Only operations that comply with the defined constraints are allowed. </li><li>Consistency: All operations must comply with the defined constraints.
</li><li>Isolation: Transactions must be completely isolated from each other. </li><li>Isolation: Transactions must be completely isolated from each other.
</li><li>Durability: Transaction committed to the database will not be lost. </li><li>Durability: Committed transaction will not be lost.
</li></ul> </li></ul>
This database also supports these properties by default, except durability, which can only
be guaranteed by other means (battery, clustering).
<h3>Atomicity</h3> <h3>Atomicity</h3>
Transactions in this database are always atomic. Transactions in this database are always atomic.
...@@ -342,19 +342,55 @@ This database is always in a consistent state. ...@@ -342,19 +342,55 @@ This database is always in a consistent state.
Referential integrity rules are always enforced. Referential integrity rules are always enforced.
<h3>Isolation</h3> <h3>Isolation</h3>
Currently, only the transaction isolation level 'serializable' is supported. For H2, the default isolation level is 'serializable', which means complete isolation.
In many database, this rule is often relaxed to provide better performance, The default transaction isolation level for many other databases is 'read committed'.
by supporting other transaction isolation levels. This provides better performance, but also means that transactions are not completely isolated.
H2 supports the transaction isolation levels 'serializable', 'read committed', and 'read uncommitted'.
<h3>Durability</h3> <h3>Durability</h3>
<p>
This database does not guarantee that all committed transactions survive a power failure. This database does not guarantee that all committed transactions survive a power failure.
If durability is required even in case of power failure, some sort of uninterruptible power supply (UPS) is required Other databases (such as Derby) claim they can guarantee it, in reality they can't.
(like using a laptop, or a battery pack). If durability is required even in case of hardware failure, Testing shows that all databases sometimes lose transactions on power failure (for details, see Durability below).
the clustering mode of this database should be used. Where losing transactions is not acceptable, a laptop or UPS (uninterruptible power supply) should be used.
</p><p> If durability is required for all possible cases of hardware failure, clustering should be used,
To achieve durability, it would be required to flush all file buffers (including system buffers) to hard disk for each commit. such as the H2 clustering mode.
In Java, there are two ways how this can be achieved:
<br /><a name="durability"></a>
<h2>Durability</h2>
<p>
Complete durability means all committed transaction survive a power failure.
Some databases claim they can guarantee complete durability, but such claims are wrong.
A durability test was run against H2, HSQLDB, PostgreSQL, and Derby.
All of those databases sometimes lost committed transactions.
The durability test is included in the H2 download, see org.h2.test.poweroff.Test.
</p>
<h3>Ways to (Not) Achive Durability</h3>
Making sure that committed transaction are not lost is more complicated than it seems first.
To guarantee complete durability, a database must ensure that the log record is on the hard drive
before the commit call returns. To do that, databases use different methods. One
is to use the 'synchronous write' option when opening a file. In Java, RandomAccessFile
supports the opening modes "rws" and "rwd":
</p>
<ul>
<li>rws: Every update to the file's content or metadata is written synchronously to the underlying storage device.
</li><li>rwd: Every update to the file's content is written synchronously to the underlying storage device.
</li>
</ul>
<p>
This feature is used by Derby and PostgreSQL.
When running a test with one of those modes (see org.h2.test.poweroff.TestWrite), around 50 thousand write operations per second are made.
Even when the operating system write buffer is disabled, the write rate is around 50 thousand operations per second.
However, this feature does not force changes to disk, because it does not flush hard drive buffers.
The test updates the same byte in the file again and again. If the hard drive was able to write at a rate of 50 thousand
operations per second, then the disk would need to make at least 50 thousand revolutions per second, or 3 million RPM
(revolutions per minute). There are no such hard drives. The hard drive used for the test is slower than 7000 RPM,
or about 116 revolutions per second. And because there is an overhead,
the maximum write rate must be lower than 116 operations per second.
</p>
<p>
There is a way to flush hard disk buffers, and therefore force data to disk: calling fsync() in the operating system.
In Java, there are two ways to call fsync:
</p> </p>
<ul> <ul>
<li>FileDescriptor.sync(). The documentation says that this will force all system buffers to synchronize with the underlying device. <li>FileDescriptor.sync(). The documentation says that this will force all system buffers to synchronize with the underlying device.
...@@ -363,44 +399,29 @@ have been written to the physical medium. ...@@ -363,44 +399,29 @@ have been written to the physical medium.
</li><li>FileChannel.force() (since JDK 1.4). This method is supposed to force any updates to this channel's file </li><li>FileChannel.force() (since JDK 1.4). This method is supposed to force any updates to this channel's file
to be written to the storage device that contains it. to be written to the storage device that contains it.
</li></ul> </li></ul>
There is one related option, but it does not force changes to disk: RandomAccessFile(.., "rws" / "rwd"):
<ul>
<li>rws: Every update to the file's content or metadata is written synchronously to the underlying storage device.
</li><li>rwd: Every update to the file's content is written synchronously to the underlying storage device.
</li></ul>
<p> <p>
A simple power-off test using two computers (they communicate over the network, and one the power By default, the MySQL database uses this feature. When using one of those methods, only around 60 write operations
is switched off on one computer) shows that the data is not always persisted to the hard drive, per second can be achieved, which is consistent with the RPM rate of the hard drive used.
even when calling FileDescriptor.sync() or FileChannel.force(). Unfortunately, even when calling FileDescriptor.sync() or FileChannel.force(),
The reason for this is that most hard drives do not obey the fsync() function. data is not always persisted to the hard drive, because most hard drives do not obey
For more information, see 'Your Hard Drive Lies to You' http://hardware.slashdot.org/article.pl?sid=05/05/13/0529252&amp;tid=198&amp;tid=128). fsync(): see 'Your Hard Drive Lies to You' http://hardware.slashdot.org/article.pl?sid=05/05/13/0529252&tid=198&tid=128.
The test was made with this database, as well as with PostgreSQL, Derby, and HSQLDB. </p>
None of those databases was able to guarantee complete transaction durability. <p>
</p><p> Currently, the only way to achieve complete durability is disabling the write cache of the operating system,
The test also shows that when calling FileDescriptor.sync() or FileChannel.force() after each file operation, using a hard drive that actually obeys the fsync() method, and using the methods FileDescriptor.sync()
only around 30 file operations per second can be made. That means, the fastest possible Java database or FileChannel.force(). The maximum number of transactions is then around 60 per second.
that calls one of those functions can reach a maximum of around 30 committed
transactions per second.
Without calling these functions, around 400000 file operations per second are possible
when using RandomAccessFile(..,"rw"), and around 2700 when using
RandomAccessFile(.., "rws"/"rwd").
</p><p>
That means that when using one of those functions, the performance goes down to at most
30 committed transactions per second, and even then there is no guarantee that transactions are durable.
These are the reasons that this database does not guarantee durability of transaction by default.
The database calls FileDescriptor.sync() when executing the SQL statement CHECKPOINT SYNC.
But by default, this database uses an asynchronous commit.
</p> </p>
<h3>Running the Durability Test</h3> <h3>Running the Durability Test</h3>
To test the durability / non-durability of this and other databases, you can use the test application To test the durability / non-durability of this and other databases, you can use the test application
in the package org.h2.test.poweroff. Two computers with network connection are required to run this test. in the package org.h2.test.poweroff. Two computers with network connection are required to run this test.
One computer acts as the listener, the test application is run on the other computer. One computer just listens, while the test application is run (and power is cut) on the other computer.
The computer with the listener application opens a TCP/IP port and listens for an incoming connection. The computer with the listener application opens a TCP/IP port and listens for an incoming connection.
The second computer first connects to the listener, and then created the databases and starts inserting The second computer first connects to the listener, and then created the databases and starts inserting
records. The connection is set to 'autocommit', which means after each inserted record a commit is performed records. The connection is set to 'autocommit', which means after each inserted record a commit is performed
automatically. Afterwards, the test computer notifies the listener that this record was inserted successfully. automatically. Afterwards, the test computer notifies the listener that this record was inserted successfully.
The listener computer displays the last inserted record number every 10 seconds. Now, the power needs The listener computer displays the last inserted record number every 10 seconds. Now, the power needs
to be switched off manually while the test is still running. Now you can restart the computer, and to be switched off manually while the test is running. Then restart the computer, and
run the application again. You will find out that in most cases, none of the databases contains all the run the application again. You will find out that in most cases, none of the databases contains all the
records that the listener computer knows about. For details, please consult the source code of the records that the listener computer knows about. For details, please consult the source code of the
listener and test application. listener and test application.
......
...@@ -263,6 +263,12 @@ It looks like the development of this database has stopped. The last release was ...@@ -263,6 +263,12 @@ It looks like the development of this database has stopped. The last release was
</tr><tr> </tr><tr>
<td><a href="http://www.polepos.org">PolePosition</a></td> <td><a href="http://www.polepos.org">PolePosition</a></td>
<td>Open source database benchmark.</td> <td>Open source database benchmark.</td>
</tr><tr>
<td><a href="http://scriptella.javaforge.com">Scriptella</a></td>
<td>ETL (Extract-Transform-Load) and script execution tool.</td>
</tr><tr>
<td><a href="http://semmle.com">SemmleCode</a></td>
<td>Eclipse plugin to help you improve software quality.</td>
</tr><tr> </tr><tr>
<td><a href="http://www.shellbook.com">Shellbook</a></td> <td><a href="http://www.shellbook.com">Shellbook</a></td>
<td>Desktop publishing application.</td> <td>Desktop publishing application.</td>
...@@ -278,6 +284,9 @@ It looks like the development of this database has stopped. The last release was ...@@ -278,6 +284,9 @@ It looks like the development of this database has stopped. The last release was
</tr><tr> </tr><tr>
<td><a href="http://www.streamcruncher.com">StreamCruncher</a></td> <td><a href="http://www.streamcruncher.com">StreamCruncher</a></td>
<td>Event (stream) processing kernel.</td> <td>Event (stream) processing kernel.</td>
</tr><tr>
<td><a href="http://www.tamava.com">Tamava</a></td>
<td>Newsgroups Reader.</td>
</tr><tr> </tr><tr>
<td><a href="http://www.webofweb.net">Web of Web</a></td> <td><a href="http://www.webofweb.net">Web of Web</a></td>
<td>Collaborative and realtime interactive media platform for the web.</td> <td>Collaborative and realtime interactive media platform for the web.</td>
...@@ -452,10 +461,13 @@ This means the database to be opened is private. In this case, the database URL ...@@ -452,10 +461,13 @@ This means the database to be opened is private. In this case, the database URL
<code>jdbc:h2:mem:</code> Opening two connections within the same virtual machine <code>jdbc:h2:mem:</code> Opening two connections within the same virtual machine
means opening two different (private) databases. means opening two different (private) databases.
</p><p> </p><p>
Some times multiple connections to the same memory-only database are required. Sometimes multiple connections to the same memory-only database are required.
In this case, the database URL must include a name. Example: <code>jdbc:h2:mem:db1</code> In this case, the database URL must include a name. Example: <code>jdbc:h2:mem:db1</code>.
Accessing the same database in this way only works within the same virtual machine and
class loader environment.
</p><p> </p><p>
It is also possible to open a memory-only database remotely using TCP/IP or SSL/TLS. It is also possible to access a memory-only database remotely
(or from multiple processes in the same machine) using TCP/IP or SSL/TLS.
An example database URL is: <code>jdbc:h2:tcp://localhost/mem:db1</code> An example database URL is: <code>jdbc:h2:tcp://localhost/mem:db1</code>
(using private database remotely is also possible). (using private database remotely is also possible).
</p> </p>
......
...@@ -37,7 +37,20 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch. ...@@ -37,7 +37,20 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
<h3>Version 1.0 (Current)</h3> <h3>Version 1.0 (Current)</h3>
<h3>Version 1.0 / 2007-TODO</h3><ul> <h3>Version 1.0 / 2007-TODO</h3><ul>
<li>Linked tables can now emit UPDATE statements if 'EMIT UPDATES' is specified in the CREATE LINKED <li>In many situations, views did not use an index if they could have. Fixed. Also the explain plan for views works now.
</li><li>The table id (important for LOB files) is now included in INFORMATION_SCHEMA.TABLES.
</li><li>When using DISTINCT, ORDER BY a function works now as long as it is in the column list.
</li><li>Support for the data type CHAR. The difference to VARCHAR is: trailing spaces are ignored. This
data type is supported for compatibility with other databases and older applications.
</li><li>The aggregate function COUNT(...) now returns a long instead of an int.
</li><li>The 'ordering' of data types was not always correct, for example an operation involving REAL and DOUBLE produced a result of type REAL. Fixed.
</li><li>CSV tool: If the same instance was used for reading and writing, the tool wrote the column names twice. Fixed.
</li><li>Compatibility: Support for the data type notation CHARACTER VARYING
</li><li>Can now ORDER BY -1 (meaning order by first column, descending), and ORDER BY ? (parameterized column number).
</li><li>Databases with invalid linked tables (for example, because the target database is not accessible) can now be opened.
Old table links don't work however.
</li><li>There was a small memory leak in the trace module. One object per opened connection was kept in a hash map.
</li><li>Linked tables can now emit UPDATE statements if 'EMIT UPDATES' is specified in the CREATE LINKED
TABLE statement. So far, updating a row always deleted the old row and then inserted the new row. TABLE statement. So far, updating a row always deleted the old row and then inserted the new row.
</li><li>In the last release, the H2 Console opened two connection when logging into a database, </li><li>In the last release, the H2 Console opened two connection when logging into a database,
and only closed one connection when logging out. Fixed. and only closed one connection when logging out. Fixed.
...@@ -1160,7 +1173,7 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch. ...@@ -1160,7 +1173,7 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
<li>Improve test code coverage <li>Improve test code coverage
</li><li>More fuzz tests </li><li>More fuzz tests
</li><li>Test very large databases and LOBs (up to 256 GB) </li><li>Test very large databases and LOBs (up to 256 GB)
</li><li>Test Multi-Threaded in-memory db access </li><li>Test multi-threaded in-memory db access
</li></ul> </li></ul>
<h3>In Version 1.1</h3> <h3>In Version 1.1</h3>
...@@ -1199,10 +1212,10 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch. ...@@ -1199,10 +1212,10 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
</li><li>SET variable { TO | = } { value | 'value' | DEFAULT } </li><li>SET variable { TO | = } { value | 'value' | DEFAULT }
</li><li>Running totals: select @running:=if(@previous=t.ID,@running,0)+t.NUM as TOTAL, @previous:=t.ID </li><li>Running totals: select @running:=if(@previous=t.ID,@running,0)+t.NUM as TOTAL, @previous:=t.ID
</li><li>Support SET REFERENTIAL_INTEGRITY {TRUE|FALSE} </li><li>Support SET REFERENTIAL_INTEGRITY {TRUE|FALSE}
</li><li>Support CHAR data type (internally use VARCHAR, but report CHAR for JPox)
</li><li>Better support large transactions, large updates / deletes: use less memory </li><li>Better support large transactions, large updates / deletes: use less memory
</li><li>Better support large transactions, large updates / deletes: allow tables without primary key </li><li>Better support large transactions, large updates / deletes: allow tables without primary key
</li><li>Support Oracle RPAD and LPAD(string, n[, pad]) (truncate the end if longer) </li><li>Support Oracle RPAD and LPAD(string, n[, pad]) (truncate the end if longer)
</li><li>Allow editing NULL values in the Console
</li></ul> </li></ul>
<h3>Priority 2</h3> <h3>Priority 2</h3>
...@@ -1265,7 +1278,6 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch. ...@@ -1265,7 +1278,6 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
</li><li>Reduce disk space usage (Derby uses less disk space?) </li><li>Reduce disk space usage (Derby uses less disk space?)
</li><li>Events for: Database Startup, Connections, Login attempts, Disconnections, Prepare (after parsing), Web Server (see http://docs.openlinksw.com/virtuoso/fn_dbev_startup.html) </li><li>Events for: Database Startup, Connections, Login attempts, Disconnections, Prepare (after parsing), Web Server (see http://docs.openlinksw.com/virtuoso/fn_dbev_startup.html)
</li><li>Optimization: Log compression </li><li>Optimization: Log compression
</li><li>Allow editing NULL values in the Console
</li><li>Compatibility: in MySQL, HSQLDB, /0.0 is NULL; in PostgreSQL, Derby: Division by zero </li><li>Compatibility: in MySQL, HSQLDB, /0.0 is NULL; in PostgreSQL, Derby: Division by zero
</li><li>Functional tables should accept parameters from other tables (see FunctionMultiReturn) </li><li>Functional tables should accept parameters from other tables (see FunctionMultiReturn)
SELECT * FROM TEST T, P2C(T.A, T.R) SELECT * FROM TEST T, P2C(T.A, T.R)
...@@ -1447,6 +1459,13 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch. ...@@ -1447,6 +1459,13 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
</li><li>Improve trace feature: add replay functionality </li><li>Improve trace feature: add replay functionality
</li><li>DatabaseEventListener: callback for all operations (including expected time, RUNSCRIPT) and cancel functionality </li><li>DatabaseEventListener: callback for all operations (including expected time, RUNSCRIPT) and cancel functionality
</li><li>H2 Console / large result sets: use 'streaming' instead of building the page in-memory </li><li>H2 Console / large result sets: use 'streaming' instead of building the page in-memory
</li><li>Benchmark: add a graph to show how databases scale (performance/database size)
</li><li>Implement a SQLData interface to map your data over to a custom object
</li><li>Extend H2 Console to run tools (show command line as well)
</li><li>Make DDL (Data Definition) operations transactional
</li><li>Sequence: add features [NO] MINVALUE, MAXVALUE, CACHE, CYCLE
</li><li>Allow execution time prepare for SELECT * FROM CSVREAD(?, 'columnNameString')
</li><li>Support multiple directories (on different hard drives) for the same database
</li></ul> </li></ul>
<h3>Not Planned</h3> <h3>Not Planned</h3>
......
...@@ -19,12 +19,13 @@ Welcome to H2, the free SQL database engine. ...@@ -19,12 +19,13 @@ Welcome to H2, the free SQL database engine.
<br /> <br />
Click here to get a fast overview. Click here to get a fast overview.
<br /><br /> <br /><br />
<a href="features.html" style="font-size: 16px; font-weight: bold">Features</a>
<br />
See what this database can do.
<br /><br />
<a href="tutorial.html" style="font-size: 16px; font-weight: bold">Tutorial</a> <a href="tutorial.html" style="font-size: 16px; font-weight: bold">Tutorial</a>
<br /> <br />
Go through the samples. Go through the samples.
<br /><br />
<a href="features.html" style="font-size: 16px; font-weight: bold">Features</a>
<br />
See what this database can do and how to use these features.
<br /><br />
</div></td></tr></table></body></html> </div></td></tr></table></body></html>
...@@ -78,6 +78,8 @@ The open/close time is almost fixed, because of the file locking protocol: The e ...@@ -78,6 +78,8 @@ The open/close time is almost fixed, because of the file locking protocol: The e
<h4>HSQLDB</h4> <h4>HSQLDB</h4>
Version 1.8.0.5 was used for the test. Version 1.8.0.5 was used for the test.
Cached tables are used in this test (hsqldb.default_table_type=cached),
and the write delay is 1 second (SET WRITE_DELAY 1).
HSQLDB is fast when using simple operations. HSQLDB is fast when using simple operations.
HSQLDB is very slow in the last test (BenchC: Transactions), probably because is has a bad query optimizer. HSQLDB is very slow in the last test (BenchC: Transactions), probably because is has a bad query optimizer.
One query where HSQLDB is slow is a two-table join: One query where HSQLDB is slow is a two-table join:
...@@ -91,7 +93,8 @@ A disadvantage in HSQLDB is the slow startup / shutdown time (currently not list ...@@ -91,7 +93,8 @@ A disadvantage in HSQLDB is the slow startup / shutdown time (currently not list
The reason is, a backup of the database is created whenever the database is opened or closed. The reason is, a backup of the database is created whenever the database is opened or closed.
<h4>Derby</h4> <h4>Derby</h4>
Version 10.2.1.6 was used for the test. Version 10.2.1.6 was used for the test. The cache size is set to 8192 pages by calling
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.storage.pageSize', '8192').
Derby is clearly the slowest embedded database in this test. Derby is clearly the slowest embedded database in this test.
This seems to be a structural problem, This seems to be a structural problem,
because all operations are really slow. It will not be easy for the developers of because all operations are really slow. It will not be easy for the developers of
...@@ -102,7 +105,7 @@ Version 8.1.4 was used for the test. ...@@ -102,7 +105,7 @@ Version 8.1.4 was used for the test.
The following options where changed in postgresql.conf: The following options where changed in postgresql.conf:
fsync = off, commit_delay = 1000. fsync = off, commit_delay = 1000.
PostgreSQL is run in server mode. It looks like the base performance is slower than PostgreSQL is run in server mode. It looks like the base performance is slower than
MySQL, but the reason could be the network layer. MySQL, the reason could be the network layer.
The memory usage number is incorrect, because only the memory usage of the JDBC driver is measured. The memory usage number is incorrect, because only the memory usage of the JDBC driver is measured.
<h4>MySQL</h4> <h4>MySQL</h4>
...@@ -182,7 +185,8 @@ think about the effect. Many database suggest to 'batch' operations when possibl ...@@ -182,7 +185,8 @@ think about the effect. Many database suggest to 'batch' operations when possibl
This benchmark switches off autocommit when loading the data, and calls commit after each 1000 This benchmark switches off autocommit when loading the data, and calls commit after each 1000
inserts. However many applications need 'short' transactions at runtime (a commit after each update). inserts. However many applications need 'short' transactions at runtime (a commit after each update).
This benchmark commits after each update / delete in the simple benchmark, and after each This benchmark commits after each update / delete in the simple benchmark, and after each
business transaction in the other benchmarks. business transaction in the other benchmarks. For databases that support delayed commits,
a delay of one second is used.
<h4>Using Prepared Statements</h4> <h4>Using Prepared Statements</h4>
Wherever possible, the test cases use prepared statements. Wherever possible, the test cases use prepared statements.
......
...@@ -217,7 +217,8 @@ Please note that in this database, user names are not case sensitive, but passwo ...@@ -217,7 +217,8 @@ Please note that in this database, user names are not case sensitive, but passwo
<br /><a name="creating_new_databases"></a> <br /><a name="creating_new_databases"></a>
<h2>Creating New Databases</h2> <h2>Creating New Databases</h2>
By default, if the database specified in the URL does not yet exist, a new (empty) By default, if the database specified in the URL does not yet exist, a new (empty)
database is created automatically. database is created automatically. The user that created the database automatically becomes
the administrator of this database.
<br /><a name="using_server"></a> <br /><a name="using_server"></a>
<h2>Using the Server</h2> <h2>Using the Server</h2>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论