提交 7dff79ad authored 作者: Thomas Mueller's avatar Thomas Mueller

New experimental query cache.

上级 12048e0f
...@@ -18,7 +18,15 @@ Change Log ...@@ -18,7 +18,15 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>Conditions with many OR operations could throw an UnsupportedOperationException if <ul><li>New experimental query cache.
The cache is only used if the SQL statement and all parameters match.
Each session has it's own cache with the given size.
Only the last returned result per query is cached.
Only SELECT statements are cached (excluding UNION and FOR UPDATE statements).
This works for both statements and prepared statement.
To enable, set the system property h2.commandCacheSize to a value larger than 0.
There is currently no plan to enable this option by default in future versions.
</li><li>Conditions with many OR operations could throw an UnsupportedOperationException if
h2.optimizeOr was enabled. h2.optimizeOr was enabled.
</li><li>XA connection: after transaction commit or rollback, </li><li>XA connection: after transaction commit or rollback,
the physical connection is set into autocommit mode. the physical connection is set into autocommit mode.
...@@ -32,6 +40,9 @@ Change Log ...@@ -32,6 +40,9 @@ Change Log
The API was unified a bit. The API was unified a bit.
</li><li>ODBC: MS Access could not link to a table with unique index or primary key. </li><li>ODBC: MS Access could not link to a table with unique index or primary key.
To solve this problem, index meta data is currently disabled for ODBC. To solve this problem, index meta data is currently disabled for ODBC.
When using the new H2 version to access a database over ODBC, the PostgreSQL catalog
is automatically upgraded. Using a database with an upgraded PostgreSQL catalog
with an older version of H2 is still possible, but not over ODBC.
</li><li>ODBC: MS Access could not link to a table with a name containing '_'. </li><li>ODBC: MS Access could not link to a table with a name containing '_'.
</li><li>ODBC: additional connection settings can now be added to the database name. </li><li>ODBC: additional connection settings can now be added to the database name.
Example: ~/test;cipher=xtea. Therefore, encrypted databases are supported. Example: ~/test;cipher=xtea. Therefore, encrypted databases are supported.
......
...@@ -43,7 +43,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -43,7 +43,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
<h2>Priority 1</h2> <h2>Priority 1</h2>
<ul><li>Bugfixes <ul><li>Bugfixes
</li><li>More tests with MULTI_THREADED=1 </li><li>More tests with MULTI_THREADED=1
</li><li>Optimization: result set caching (like MySQL); option to disable
</li><li>Server side cursors </li><li>Server side cursors
</li></ul> </li></ul>
......
...@@ -261,4 +261,9 @@ public abstract class Command implements CommandInterface { ...@@ -261,4 +261,9 @@ public abstract class Command implements CommandInterface {
public String toString() { public String toString() {
return TraceObject.toString(sql, getParameters()); return TraceObject.toString(sql, getParameters());
} }
public boolean isCacheable() {
return false;
}
} }
...@@ -45,7 +45,7 @@ public class CommandContainer extends Command { ...@@ -45,7 +45,7 @@ public class CommandContainer extends Command {
String sql = prepared.getSQL(); String sql = prepared.getSQL();
ArrayList<Parameter> oldParams = prepared.getParameters(); ArrayList<Parameter> oldParams = prepared.getParameters();
Parser parser = new Parser(session); Parser parser = new Parser(session);
prepared = parser.parseOnly(sql); prepared = parser.parse(sql);
long mod = prepared.getModificationMetaId(); long mod = prepared.getModificationMetaId();
prepared.setModificationMetaId(0); prepared.setModificationMetaId(0);
ArrayList<Parameter> newParams = prepared.getParameters(); ArrayList<Parameter> newParams = prepared.getParameters();
...@@ -90,4 +90,8 @@ public class CommandContainer extends Command { ...@@ -90,4 +90,8 @@ public class CommandContainer extends Command {
return prepared.queryMeta(); return prepared.queryMeta();
} }
public boolean isCacheable() {
return prepared.isCacheable();
}
} }
...@@ -204,16 +204,6 @@ public class Parser { ...@@ -204,16 +204,6 @@ public class Parser {
return p; return p;
} }
/**
* Parse the statement, but don't prepare it for execution.
*
* @param sql the SQL statement to parse
* @return the prepared object
*/
public Prepared parseOnly(String sql) {
return parse(sql);
}
/** /**
* Parse a statement or a list of statements, and prepare it for execution. * Parse a statement or a list of statements, and prepare it for execution.
* *
...@@ -222,8 +212,7 @@ public class Parser { ...@@ -222,8 +212,7 @@ public class Parser {
*/ */
public Command prepareCommand(String sql) { public Command prepareCommand(String sql) {
try { try {
Prepared p = parse(sql); Prepared p = prepare(sql);
p.prepare();
Command c = new CommandContainer(this, sql, p); Command c = new CommandContainer(this, sql, p);
p.setCommand(c); p.setCommand(c);
if (isToken(";")) { if (isToken(";")) {
...@@ -246,7 +235,13 @@ public class Parser { ...@@ -246,7 +235,13 @@ public class Parser {
} }
} }
private Prepared parse(String sql) { /**
* Parse the statement, but don't prepare it for execution.
*
* @param sql the SQL statement to parse
* @return the prepared object
*/
Prepared parse(String sql) {
Prepared p; Prepared p;
try { try {
// first, try the fast variant // first, try the fast variant
......
...@@ -410,4 +410,8 @@ public abstract class Prepared { ...@@ -410,4 +410,8 @@ public abstract class Prepared {
return e.addSQL(buff.toString()); return e.addSQL(buff.toString());
} }
public boolean isCacheable() {
return false;
}
} }
...@@ -55,6 +55,7 @@ public abstract class Query extends Prepared { ...@@ -55,6 +55,7 @@ public abstract class Query extends Prepared {
private long lastEvaluated; private long lastEvaluated;
private LocalResult lastResult; private LocalResult lastResult;
private Value[] lastParameters; private Value[] lastParameters;
private boolean cacheableChecked, cacheable;
public Query(Session session) { public Query(Session session) {
super(session); super(session);
...@@ -180,8 +181,15 @@ public abstract class Query extends Prepared { ...@@ -180,8 +181,15 @@ public abstract class Query extends Prepared {
return true; return true;
} }
private boolean sameResultAsLast(Session s, Value[] params, Value[] lastParams, long lastEval) private boolean sameResultAsLast(Session s, Value[] params, Value[] lastParams, long lastEval) {
{ if (!cacheableChecked) {
long max = getMaxDataModificationId();
cacheable = max != Long.MAX_VALUE;
cacheableChecked = true;
}
if (!cacheable) {
return false;
}
Database db = s.getDatabase(); Database db = s.getDatabase();
for (int i = 0; i < params.length; i++) { for (int i = 0; i < params.length; i++) {
if (!db.areEqual(lastParams[i], params[i])) { if (!db.areEqual(lastParams[i], params[i])) {
......
...@@ -1167,4 +1167,9 @@ public class Select extends Query { ...@@ -1167,4 +1167,9 @@ public class Select extends Query {
return isEverything(ExpressionVisitor.READONLY); return isEverything(ExpressionVisitor.READONLY);
} }
public boolean isCacheable() {
return !isForUpdate;
}
} }
...@@ -350,6 +350,9 @@ public class Set extends Prepared { ...@@ -350,6 +350,9 @@ public class Set extends Prepared {
} }
// the meta data information has changed // the meta data information has changed
database.getNextModificationDataId(); database.getNextModificationDataId();
// query caches might be affected as well, for example
// when changing the compatibility mode
database.getNextModificationMetaId();
return 0; return 0;
} }
......
...@@ -180,6 +180,16 @@ public class SysProperties { ...@@ -180,6 +180,16 @@ public class SysProperties {
*/ */
public static final String CLIENT_TRACE_DIRECTORY = getStringSetting("h2.clientTraceDirectory", "trace.db/"); public static final String CLIENT_TRACE_DIRECTORY = getStringSetting("h2.clientTraceDirectory", "trace.db/");
/**
* System property <code>h2.commandCacheSize</code> (default: 0).<br />
* The size of the query cache. Each session has it's own cache with the
* given size. The cache is only used if the SQL statement and all
* parameters match. Only the last returned result per query is cached. Only
* SELECT statements are cached (excluding UNION and FOR UPDATE statements).
* This works for both statements and prepared statement.
*/
public static final int QUERY_CACHE_SIZE = getIntSetting("h2.queryCacheSize", 0);
/** /**
* System property <code>h2.consoleStream</code> (default: true).<br /> * System property <code>h2.consoleStream</code> (default: true).<br />
* H2 Console: stream query results. * H2 Console: stream query results.
......
...@@ -31,6 +31,7 @@ import org.h2.store.InDoubtTransaction; ...@@ -31,6 +31,7 @@ import org.h2.store.InDoubtTransaction;
import org.h2.store.LobStorage; import org.h2.store.LobStorage;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.SmallLRUCache;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
...@@ -100,6 +101,8 @@ public class Session extends SessionWithState implements SessionFactory { ...@@ -100,6 +101,8 @@ public class Session extends SessionWithState implements SessionFactory {
private int modificationId; private int modificationId;
private int modificationIdState; private int modificationIdState;
private int objectId; private int objectId;
private int queryCacheSize = SysProperties.QUERY_CACHE_SIZE;
private SmallLRUCache<String, Command> queryCache;
public Session() { public Session() {
// to create a new session using the factory // to create a new session using the factory
...@@ -416,8 +419,25 @@ public class Session extends SessionWithState implements SessionFactory { ...@@ -416,8 +419,25 @@ public class Session extends SessionWithState implements SessionFactory {
if (closed) { if (closed) {
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed"); throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed");
} }
Command command;
if (queryCacheSize > 0) {
if (queryCache == null) {
queryCache = SmallLRUCache.newInstance(queryCacheSize);
} else {
command = queryCache.get(sql);
if (command != null) {
return command;
}
}
}
Parser parser = new Parser(this); Parser parser = new Parser(this);
return parser.prepareCommand(sql); command = parser.prepareCommand(sql);
if (queryCache != null) {
if (command.isCacheable()) {
queryCache.put(sql, command);
}
}
return command;
} }
public Database getDatabase() { public Database getDatabase() {
......
...@@ -1747,6 +1747,14 @@ public class MetaTable extends Table { ...@@ -1747,6 +1747,14 @@ public class MetaTable extends Table {
} }
public long getMaxDataModificationId() { public long getMaxDataModificationId() {
switch (type) {
case SETTINGS:
case IN_DOUBT:
case SESSIONS:
case LOCKS:
case SESSION_STATE:
return Long.MAX_VALUE;
}
return database.getModificationDataId(); return database.getModificationDataId();
} }
......
...@@ -44,6 +44,7 @@ import org.h2.test.db.TestOpenClose; ...@@ -44,6 +44,7 @@ import org.h2.test.db.TestOpenClose;
import org.h2.test.db.TestOptimizations; import org.h2.test.db.TestOptimizations;
import org.h2.test.db.TestOutOfMemory; import org.h2.test.db.TestOutOfMemory;
import org.h2.test.db.TestPowerOff; import org.h2.test.db.TestPowerOff;
import org.h2.test.db.TestQueryCache;
import org.h2.test.db.TestReadOnly; import org.h2.test.db.TestReadOnly;
import org.h2.test.db.TestRights; import org.h2.test.db.TestRights;
import org.h2.test.db.TestRunscript; import org.h2.test.db.TestRunscript;
...@@ -308,7 +309,8 @@ java org.h2.test.TestAll timer ...@@ -308,7 +309,8 @@ java org.h2.test.TestAll timer
// System.setProperty("h2.analyzeAuto", "100"); // System.setProperty("h2.analyzeAuto", "100");
// System.setProperty("h2.nestedJoins", "true"); // System.setProperty("h2.nestedJoins", "true");
// System.setProperty("h2.optimizeOr", "true"); // System.setProperty("h2.optimizeOr", "true");
// System.setProperty("h2.dropRestrict", "true"); // System.setProperty("h2.queryCacheSize", "100");
// System.setProperty("h2.dropRestrict", "true");
int speedup; int speedup;
// System.setProperty("h2.syncMethod", ""); // System.setProperty("h2.syncMethod", "");
...@@ -529,6 +531,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -529,6 +531,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestOptimizations().runTest(this); new TestOptimizations().runTest(this);
new TestOutOfMemory().runTest(this); new TestOutOfMemory().runTest(this);
new TestPowerOff().runTest(this); new TestPowerOff().runTest(this);
new TestQueryCache().runTest(this);
new TestReadOnly().runTest(this); new TestReadOnly().runTest(this);
new TestRights().runTest(this); new TestRights().runTest(this);
new TestRunscript().runTest(this); new TestRunscript().runTest(this);
......
package org.h2.test.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import org.h2.test.TestBase;
/**
* Tests the query cache.
*/
public class TestQueryCache extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
System.setProperty("h2.queryCacheSize", "10");
TestBase.createCaller().init().test();
}
public void test() throws Exception {
if (Integer.parseInt(System.getProperty("h2.queryCacheSize")) <= 0) {
return;
}
deleteDb("queryCache");
Connection conn = getConnection("queryCache");
Statement stat = conn.createStatement();
stat.execute("create table test(id int, name varchar) as select x, space(100) from system_range(1, 1000)");
PreparedStatement prep = conn.prepareStatement("select count(*) from test t1, test t2");
long time;
ResultSet rs;
long first = 0;
for (int i = 0; i < 4; i++) {
// this should both ensure results are not re-used
// stat.execute("set mode regular");
// stat.execute("create table x()");
// stat.execute("drop table x");
time = System.currentTimeMillis();
prep = conn.prepareStatement("select count(*) from test t1, test t2");
rs = prep.executeQuery();
rs = stat.executeQuery("select count(*) from test t1, test t2");
rs.next();
int c = rs.getInt(1);
assertEquals(1000000, c);
time = System.currentTimeMillis() - time;
if (first == 0) {
first = time;
} else {
assertSmaller(time, first);
}
}
conn.close();
deleteDb("queryCache");
}
}
...@@ -649,4 +649,4 @@ importing cumulative fired convenient sums judged anybody vacuum encountered ...@@ -649,4 +649,4 @@ importing cumulative fired convenient sums judged anybody vacuum encountered
corresponds cnf informatique ilm boundaries shao crossed retroweaver usr pico corresponds cnf informatique ilm boundaries shao crossed retroweaver usr pico
pengxiang china timestampadd picked releasing autoboxing conversions pengxiang china timestampadd picked releasing autoboxing conversions
pagestore addon defaults introduced customized histogram transact locker activemq pagestore addon defaults introduced customized histogram transact locker activemq
iml unified regclass netbeans geqo servername creator iml unified regclass netbeans geqo servername creator eclipsecs cacheable
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论