reduce locking when using SQLiteStatement
Do compiling of sql, binding of args and execution of the SQL statement within one single database lock period. This reduces the number of times the database lock needs to be acquired during the course of compilation, binding and execution of a SQLiteStatement. Change-Id: I22b090ec9e10fc0aa2532a93bafe610af2546b58
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
package android.database.sqlite;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A base class for compiled SQLite programs.
|
||||
@@ -29,7 +32,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
private static final String TAG = "SQLiteProgram";
|
||||
|
||||
/** the type of sql statement being processed by this object */
|
||||
private static final int SELECT_STMT = 1;
|
||||
/* package */ static final int SELECT_STMT = 1;
|
||||
private static final int UPDATE_STMT = 2;
|
||||
private static final int OTHER_STMT = 3;
|
||||
|
||||
@@ -63,13 +66,40 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
@Deprecated
|
||||
protected int nStatement = 0;
|
||||
|
||||
/**
|
||||
* In the case of {@link SQLiteStatement}, this member stores the bindargs passed
|
||||
* to the following methods, instead of actually doing the binding.
|
||||
* <ul>
|
||||
* <li>{@link #bindBlob(int, byte[])}</li>
|
||||
* <li>{@link #bindDouble(int, double)}</li>
|
||||
* <li>{@link #bindLong(int, long)}</li>
|
||||
* <li>{@link #bindNull(int)}</li>
|
||||
* <li>{@link #bindString(int, String)}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Each entry in the array is a Pair of
|
||||
* <ol>
|
||||
* <li>bind arg position number</li>
|
||||
* <li>the value to be bound to the bindarg</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* It is lazily initialized in the above bind methods
|
||||
* and it is cleared in {@link #clearBindings()} method.
|
||||
* <p>
|
||||
* It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this
|
||||
*/
|
||||
private ArrayList<Pair<Integer, Object>> bindArgs = null;
|
||||
|
||||
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
|
||||
this(db, sql, true);
|
||||
}
|
||||
|
||||
/* package */ SQLiteProgram(SQLiteDatabase db, String sql, boolean compileFlag) {
|
||||
mSql = sql.trim();
|
||||
attachObjectToDatabase(db);
|
||||
db.acquireReference();
|
||||
db.addSQLiteClosable(this);
|
||||
mDatabase = db;
|
||||
nHandle = db.mNativeHandle;
|
||||
if (compileFlag) {
|
||||
compileSql();
|
||||
}
|
||||
@@ -120,7 +150,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
nStatement = mCompiledSql.nStatement;
|
||||
}
|
||||
|
||||
private int getSqlStatementType(String sql) {
|
||||
/* package */ int getSqlStatementType(String sql) {
|
||||
if (mSql.length() < 6) {
|
||||
return OTHER_STMT;
|
||||
}
|
||||
@@ -136,46 +166,11 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
return OTHER_STMT;
|
||||
}
|
||||
|
||||
private synchronized void attachObjectToDatabase(SQLiteDatabase db) {
|
||||
db.acquireReference();
|
||||
db.addSQLiteClosable(this);
|
||||
mDatabase = db;
|
||||
nHandle = db.mNativeHandle;
|
||||
}
|
||||
|
||||
private synchronized void detachObjectFromDatabase() {
|
||||
mDatabase.removeSQLiteClosable(this);
|
||||
mDatabase.releaseReference();
|
||||
}
|
||||
|
||||
/* package */ synchronized void verifyDbAndCompileSql() {
|
||||
mDatabase.verifyDbIsOpen();
|
||||
// use pooled database connection handles for SELECT SQL statements
|
||||
SQLiteDatabase db = (getSqlStatementType(mSql) != SELECT_STMT) ? mDatabase
|
||||
: mDatabase.getDbConnection(mSql);
|
||||
if (!db.equals(mDatabase)) {
|
||||
// the database connection handle to be used is not the same as the one supplied
|
||||
// in the constructor. do some housekeeping.
|
||||
detachObjectFromDatabase();
|
||||
attachObjectToDatabase(db);
|
||||
}
|
||||
// compile the sql statement
|
||||
if (nStatement > 0) {
|
||||
// already compiled.
|
||||
return;
|
||||
}
|
||||
mDatabase.lock();
|
||||
try {
|
||||
compileSql();
|
||||
} finally {
|
||||
mDatabase.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAllReferencesReleased() {
|
||||
releaseCompiledSqlIfNotInCache();
|
||||
detachObjectFromDatabase();
|
||||
mDatabase.removeSQLiteClosable(this);
|
||||
mDatabase.releaseReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,11 +241,17 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
* @param index The 1-based index to the parameter to bind null to
|
||||
*/
|
||||
public void bindNull(int index) {
|
||||
mDatabase.verifyDbIsOpen();
|
||||
synchronized (this) {
|
||||
verifyDbAndCompileSql();
|
||||
acquireReference();
|
||||
try {
|
||||
native_bind_null(index);
|
||||
if (this.nStatement == 0) {
|
||||
// since the SQL statement is not compiled, don't do the binding yet.
|
||||
// can be done before executing the SQL statement
|
||||
addToBindArgs(index, null);
|
||||
} else {
|
||||
native_bind_null(index);
|
||||
}
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
@@ -265,11 +266,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
* @param value The value to bind
|
||||
*/
|
||||
public void bindLong(int index, long value) {
|
||||
mDatabase.verifyDbIsOpen();
|
||||
synchronized (this) {
|
||||
verifyDbAndCompileSql();
|
||||
acquireReference();
|
||||
try {
|
||||
native_bind_long(index, value);
|
||||
if (this.nStatement == 0) {
|
||||
addToBindArgs(index, value);
|
||||
} else {
|
||||
native_bind_long(index, value);
|
||||
}
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
@@ -284,11 +289,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
* @param value The value to bind
|
||||
*/
|
||||
public void bindDouble(int index, double value) {
|
||||
mDatabase.verifyDbIsOpen();
|
||||
synchronized (this) {
|
||||
verifyDbAndCompileSql();
|
||||
acquireReference();
|
||||
try {
|
||||
native_bind_double(index, value);
|
||||
if (this.nStatement == 0) {
|
||||
addToBindArgs(index, value);
|
||||
} else {
|
||||
native_bind_double(index, value);
|
||||
}
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
@@ -306,11 +315,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("the bind value at index " + index + " is null");
|
||||
}
|
||||
mDatabase.verifyDbIsOpen();
|
||||
synchronized (this) {
|
||||
verifyDbAndCompileSql();
|
||||
acquireReference();
|
||||
try {
|
||||
native_bind_string(index, value);
|
||||
if (this.nStatement == 0) {
|
||||
addToBindArgs(index, value);
|
||||
} else {
|
||||
native_bind_string(index, value);
|
||||
}
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
@@ -328,11 +341,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("the bind value at index " + index + " is null");
|
||||
}
|
||||
mDatabase.verifyDbIsOpen();
|
||||
synchronized (this) {
|
||||
verifyDbAndCompileSql();
|
||||
acquireReference();
|
||||
try {
|
||||
native_bind_blob(index, value);
|
||||
if (this.nStatement == 0) {
|
||||
addToBindArgs(index, value);
|
||||
} else {
|
||||
native_bind_blob(index, value);
|
||||
}
|
||||
} finally {
|
||||
releaseReference();
|
||||
}
|
||||
@@ -344,6 +361,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
*/
|
||||
public void clearBindings() {
|
||||
synchronized (this) {
|
||||
bindArgs = null;
|
||||
if (this.nStatement == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -362,6 +380,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
*/
|
||||
public void close() {
|
||||
synchronized (this) {
|
||||
bindArgs = null;
|
||||
if (nHandle == 0 || !mDatabase.isOpen()) {
|
||||
return;
|
||||
}
|
||||
@@ -369,6 +388,34 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void addToBindArgs(int index, Object value) {
|
||||
if (bindArgs == null) {
|
||||
bindArgs = new ArrayList<Pair<Integer, Object>>();
|
||||
}
|
||||
bindArgs.add(new Pair<Integer, Object>(index, value));
|
||||
}
|
||||
|
||||
/* package */ synchronized void compileAndbindAllArgs() {
|
||||
assert nStatement == 0;
|
||||
compileSql();
|
||||
if (bindArgs == null) {
|
||||
return;
|
||||
}
|
||||
for (Pair<Integer, Object> p : bindArgs) {
|
||||
if (p.second == null) {
|
||||
native_bind_null(p.first);
|
||||
} else if (p.second instanceof Long) {
|
||||
native_bind_long(p.first, (Long)p.second);
|
||||
} else if (p.second instanceof Double) {
|
||||
native_bind_double(p.first, (Double)p.second);
|
||||
} else if (p.second instanceof byte[]) {
|
||||
native_bind_blob(p.first, (byte[])p.second);
|
||||
} else {
|
||||
native_bind_string(p.first, (String)p.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is deprecated and must not be used.
|
||||
* Compiles SQL into a SQLite program.
|
||||
|
||||
@@ -35,6 +35,8 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
private static final boolean READ = true;
|
||||
private static final boolean WRITE = false;
|
||||
|
||||
private SQLiteDatabase mOrigDb;
|
||||
|
||||
/**
|
||||
* Don't use SQLiteStatement constructor directly, please use
|
||||
* {@link SQLiteDatabase#compileStatement(String)}
|
||||
@@ -53,12 +55,14 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* some reason
|
||||
*/
|
||||
public void execute() {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +76,15 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* some reason
|
||||
*/
|
||||
public long executeInsert() {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +97,15 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
|
||||
*/
|
||||
public long simpleQueryForLong() {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
long retValue = native_1x1_long();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
long retValue = native_1x1_long();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,13 +118,15 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
|
||||
*/
|
||||
public String simpleQueryForString() {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
String retValue = native_1x1_string();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
String retValue = native_1x1_string();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +135,7 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* this method does the following:
|
||||
* <ul>
|
||||
* <li>make sure the database is open</li>
|
||||
* <li>get a database connection from the connection pool,if possible</li>
|
||||
* <li>notifies {@link BlockGuard} of read/write</li>
|
||||
* <li>get lock on the database</li>
|
||||
* <li>acquire reference on this object</li>
|
||||
@@ -135,7 +146,14 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* methods in this class.
|
||||
*/
|
||||
private long acquireAndLock(boolean rwFlag) {
|
||||
verifyDbAndCompileSql();
|
||||
// use pooled database connection handles for SELECT SQL statements
|
||||
mDatabase.verifyDbIsOpen();
|
||||
SQLiteDatabase db = (getSqlStatementType(mSql) != SELECT_STMT) ? mDatabase
|
||||
: mDatabase.getDbConnection(mSql);
|
||||
// use the database connection obtained above
|
||||
mOrigDb = mDatabase;
|
||||
mDatabase = db;
|
||||
nHandle = mDatabase.mNativeHandle;
|
||||
if (rwFlag == WRITE) {
|
||||
BlockGuard.getThreadPolicy().onWriteToDisk();
|
||||
} else {
|
||||
@@ -145,6 +163,7 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
mDatabase.lock();
|
||||
acquireReference();
|
||||
mDatabase.closePendingStatements();
|
||||
compileAndbindAllArgs();
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@@ -158,6 +177,9 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
// release the compiled sql statement so that the caller's SQLiteStatement no longer
|
||||
// has a hard reference to a database object that may get deallocated at any point.
|
||||
releaseCompiledSqlIfNotInCache();
|
||||
// restore the database connection handle to the original value
|
||||
mDatabase = mOrigDb;
|
||||
nHandle = mDatabase.mNativeHandle;
|
||||
}
|
||||
|
||||
private final native void native_execute();
|
||||
|
||||
Reference in New Issue
Block a user