Merge pull request #1810 from mfietz/issue/1749-db-performance

Enable WAL and use non-exclusive transactions
This commit is contained in:
Tom Hennen 2016-03-23 14:51:39 -04:00
commit 8a0f6cb529
6 changed files with 211 additions and 108 deletions

View File

@ -70,6 +70,7 @@ public class DBCleanupTests extends InstrumentationTestCase {
assertTrue(destFolder.canWrite()); assertTrue(destFolder.canWrite());
// create new database // create new database
PodDBAdapter.init(context);
PodDBAdapter.deleteDatabase(); PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance(); PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open(); adapter.open();

View File

@ -19,8 +19,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.storage.PodDBAdapter;
import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
/** /**
* Tests that the APNullCleanupAlgorithm is working correctly. * Tests that the APNullCleanupAlgorithm is working correctly.
*/ */
@ -60,6 +58,7 @@ public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase {
assertTrue(destFolder.canWrite()); assertTrue(destFolder.canWrite());
// create new database // create new database
PodDBAdapter.init(getInstrumentation().getTargetContext());
PodDBAdapter.deleteDatabase(); PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance(); PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open(); adapter.open();

View File

@ -34,6 +34,7 @@ public class DBReaderTest extends InstrumentationTestCase {
super.setUp(); super.setUp();
// create new database // create new database
PodDBAdapter.init(getInstrumentation().getTargetContext());
PodDBAdapter.deleteDatabase(); PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance(); PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open(); adapter.open();

View File

@ -28,7 +28,6 @@ public class DBTasksTest extends InstrumentationTestCase {
@Override @Override
protected void tearDown() throws Exception { protected void tearDown() throws Exception {
super.tearDown(); super.tearDown();
assertTrue(PodDBAdapter.deleteDatabase()); assertTrue(PodDBAdapter.deleteDatabase());
} }
@ -38,6 +37,7 @@ public class DBTasksTest extends InstrumentationTestCase {
context = getInstrumentation().getTargetContext(); context = getInstrumentation().getTargetContext();
// create new database // create new database
PodDBAdapter.init(getInstrumentation().getTargetContext());
PodDBAdapter.deleteDatabase(); PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance(); PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open(); adapter.open();

View File

@ -53,6 +53,7 @@ public class DBWriterTest extends InstrumentationTestCase {
super.setUp(); super.setUp();
// create new database // create new database
PodDBAdapter.init(getInstrumentation().getTargetContext());
PodDBAdapter.deleteDatabase(); PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance(); PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open(); adapter.open();
@ -194,6 +195,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertEquals(0, c.getCount()); assertEquals(0, c.getCount());
c.close(); c.close();
} }
adapter.close();
} }
public void testDeleteFeedNoImage() throws ExecutionException, InterruptedException, IOException, TimeoutException { public void testDeleteFeedNoImage() throws ExecutionException, InterruptedException, IOException, TimeoutException {
@ -250,6 +252,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(c.getCount() == 0); assertTrue(c.getCount() == 0);
c.close(); c.close();
} }
adapter.close();
} }
public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException { public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException {
@ -287,6 +290,7 @@ public class DBWriterTest extends InstrumentationTestCase {
c = adapter.getImageCursor(String.valueOf(image.getId())); c = adapter.getImageCursor(String.valueOf(image.getId()));
assertTrue(c.getCount() == 0); assertTrue(c.getCount() == 0);
c.close(); c.close();
adapter.close();
} }
public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException { public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException {
@ -339,6 +343,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(c.getCount() == 0); assertTrue(c.getCount() == 0);
c.close(); c.close();
} }
adapter.close();
} }
public void testDeleteFeedWithItemImages() throws InterruptedException, ExecutionException, TimeoutException, IOException { public void testDeleteFeedWithItemImages() throws InterruptedException, ExecutionException, TimeoutException, IOException {
@ -397,6 +402,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertEquals(0, c.getCount()); assertEquals(0, c.getCount());
c.close(); c.close();
} }
adapter.close();
} }
public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException { public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException {
@ -527,6 +533,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(c.getCount() == 0); assertTrue(c.getCount() == 0);
c.close(); c.close();
} }
adapter.close();
} }
private FeedMedia playbackHistorySetup(Date playbackCompletionDate) { private FeedMedia playbackHistorySetup(Date playbackCompletionDate) {
@ -730,7 +737,6 @@ public class DBWriterTest extends InstrumentationTestCase {
} }
assertTrue(idFound); assertTrue(idFound);
} }
queue.close(); queue.close();
adapter.close(); adapter.close();
} }

View File

@ -10,12 +10,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.media.MediaMetadataRetriever; import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.ProgressEvent; import de.danoeh.antennapod.core.event.ProgressEvent;
@ -303,7 +305,7 @@ public class PodDBAdapter {
private static SQLiteDatabase db; private static SQLiteDatabase db;
private static Context context; private static Context context;
private static PodDBHelper dbHelper; private static PodDBHelper dbHelper;
private static int counter = 0; private static AtomicInteger counter = new AtomicInteger(0);
public static void init(Context context) { public static void init(Context context) {
PodDBAdapter.context = context.getApplicationContext(); PodDBAdapter.context = context.getApplicationContext();
@ -318,11 +320,15 @@ public class PodDBAdapter {
private PodDBAdapter() {} private PodDBAdapter() {}
public PodDBAdapter open() { public synchronized PodDBAdapter open() {
int adapters = counter.incrementAndGet();
Log.v(TAG, "Opening DB #" + adapters);
if (db == null || !db.isOpen() || db.isReadOnly()) { if (db == null || !db.isOpen() || db.isReadOnly()) {
Log.v(TAG, "Opening DB");
try { try {
db = dbHelper.getWritableDatabase(); db = dbHelper.getWritableDatabase();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
db.enableWriteAheadLogging();
}
} catch (SQLException ex) { } catch (SQLException ex) {
Log.e(TAG, Log.getStackTraceString(ex)); Log.e(TAG, Log.getStackTraceString(ex));
db = dbHelper.getReadableDatabase(); db = dbHelper.getReadableDatabase();
@ -331,8 +337,13 @@ public class PodDBAdapter {
return this; return this;
} }
public void close() { public synchronized void close() {
// do nothing int adapters = counter.decrementAndGet();
Log.v(TAG, "Closing DB #" + adapters);
if(adapters == 0) {
Log.v(TAG, "Closing DB, really");
db.close();
}
} }
public static boolean deleteDatabase() { public static boolean deleteDatabase() {
@ -425,34 +436,46 @@ public class PodDBAdapter {
*/ */
public long setImage(FeedImage image) { public long setImage(FeedImage image) {
boolean startedTransaction = false; boolean startedTransaction = false;
if(!db.inTransaction()) {
db.beginTransaction();
startedTransaction = true;
}
ContentValues values = new ContentValues(); try {
values.put(KEY_TITLE, image.getTitle()); if (!db.inTransaction()) {
values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
values.put(KEY_DOWNLOADED, image.isDownloaded()); db.beginTransactionNonExclusive();
values.put(KEY_FILE_URL, image.getFile_url()); } else {
if (image.getId() == 0) { db.beginTransaction();
image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values)); }
} else { startedTransaction = true;
db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", }
new String[]{String.valueOf(image.getId())});
} ContentValues values = new ContentValues();
values.put(KEY_TITLE, image.getTitle());
final FeedComponent owner = image.getOwner(); values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
if (owner != null && owner.getId() != 0) { values.put(KEY_DOWNLOADED, image.isDownloaded());
values.clear(); values.put(KEY_FILE_URL, image.getFile_url());
values.put(KEY_IMAGE, image.getId()); if (image.getId() == 0) {
if (owner instanceof Feed) { image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())}); } else {
db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
new String[]{String.valueOf(image.getId())});
}
final FeedComponent owner = image.getOwner();
if (owner != null && owner.getId() != 0) {
values.clear();
values.put(KEY_IMAGE, image.getId());
if (owner instanceof Feed) {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())});
}
}
if (startedTransaction) {
db.setTransactionSuccessful();
}
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
if (startedTransaction) {
db.endTransaction();
} }
}
if(startedTransaction) {
db.setTransactionSuccessful();
db.endTransaction();
} }
return image.getId(); return image.getId();
} }
@ -522,20 +545,29 @@ public class PodDBAdapter {
* transaction * transaction
*/ */
public void setCompleteFeed(Feed... feeds) { public void setCompleteFeed(Feed... feeds) {
db.beginTransaction(); try {
for (Feed feed : feeds) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setFeed(feed); db.beginTransactionNonExclusive();
if (feed.getItems() != null) { } else {
for (FeedItem item : feed.getItems()) { db.beginTransaction();
setFeedItem(item, false); }
for (Feed feed : feeds) {
setFeed(feed);
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
setFeedItem(item, false);
}
}
if (feed.getPreferences() != null) {
setFeedPreferences(feed.getPreferences());
} }
} }
if (feed.getPreferences() != null) { db.setTransactionSuccessful();
setFeedPreferences(feed.getPreferences()); } catch (SQLException e) {
} Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
} }
db.setTransactionSuccessful();
db.endTransaction();
} }
/** /**
@ -598,19 +630,38 @@ public class PodDBAdapter {
} }
public void setFeedItemlist(List<FeedItem> items) { public void setFeedItemlist(List<FeedItem> items) {
db.beginTransaction(); try {
for (FeedItem item : items) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setFeedItem(item, true); db.beginTransactionNonExclusive();
} else {
db.beginTransaction();
}
for (FeedItem item : items) {
setFeedItem(item, true);
}
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
} }
db.setTransactionSuccessful();
db.endTransaction();
} }
public long setSingleFeedItem(FeedItem item) { public long setSingleFeedItem(FeedItem item) {
db.beginTransaction(); long result = 0;
long result = setFeedItem(item, true); try {
db.setTransactionSuccessful(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
db.endTransaction(); db.beginTransactionNonExclusive();
} else {
db.beginTransaction();
}
result = setFeedItem(item, true);
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
}
return result; return result;
} }
@ -728,20 +779,29 @@ public class PodDBAdapter {
public void setFeedItemRead(int played, long itemId, long mediaId, public void setFeedItemRead(int played, long itemId, long mediaId,
boolean resetMediaPosition) { boolean resetMediaPosition) {
db.beginTransaction(); try {
ContentValues values = new ContentValues(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
db.beginTransactionNonExclusive();
} else {
db.beginTransaction();
}
ContentValues values = new ContentValues();
values.put(KEY_READ, played); values.put(KEY_READ, played);
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)}); db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)});
if (resetMediaPosition) { if (resetMediaPosition) {
values.clear(); values.clear();
values.put(KEY_POSITION, 0); values.put(KEY_POSITION, 0);
db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
}
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
} }
db.setTransactionSuccessful();
db.endTransaction();
} }
/** /**
@ -750,15 +810,24 @@ public class PodDBAdapter {
* @param itemIds items to change the value of * @param itemIds items to change the value of
*/ */
public void setFeedItemRead(int read, long... itemIds) { public void setFeedItemRead(int read, long... itemIds) {
db.beginTransaction(); try {
ContentValues values = new ContentValues(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
for (long id : itemIds) { db.beginTransactionNonExclusive();
values.clear(); } else {
values.put(KEY_READ, read); db.beginTransaction();
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)}); }
ContentValues values = new ContentValues();
for (long id : itemIds) {
values.clear();
values.put(KEY_READ, read);
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)});
}
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
} }
db.setTransactionSuccessful();
db.endTransaction();
} }
public void setChapters(FeedItem item) { public void setChapters(FeedItem item) {
@ -822,17 +891,26 @@ public class PodDBAdapter {
public void setFavorites(List<FeedItem> favorites) { public void setFavorites(List<FeedItem> favorites) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
db.beginTransaction(); try {
db.delete(TABLE_NAME_FAVORITES, null, null); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
for (int i = 0; i < favorites.size(); i++) { db.beginTransactionNonExclusive();
FeedItem item = favorites.get(i); } else {
values.put(KEY_ID, i); db.beginTransaction();
values.put(KEY_FEEDITEM, item.getId()); }
values.put(KEY_FEED, item.getFeed().getId()); db.delete(TABLE_NAME_FAVORITES, null, null);
db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE); for (int i = 0; i < favorites.size(); i++) {
FeedItem item = favorites.get(i);
values.put(KEY_ID, i);
values.put(KEY_FEEDITEM, item.getId());
values.put(KEY_FEED, item.getFeed().getId());
db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
} }
db.setTransactionSuccessful();
db.endTransaction();
} }
/** /**
@ -880,17 +958,26 @@ public class PodDBAdapter {
public void setQueue(List<FeedItem> queue) { public void setQueue(List<FeedItem> queue) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
db.beginTransaction(); try {
db.delete(TABLE_NAME_QUEUE, null, null); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
for (int i = 0; i < queue.size(); i++) { db.beginTransactionNonExclusive();
FeedItem item = queue.get(i); } else {
values.put(KEY_ID, i); db.beginTransaction();
values.put(KEY_FEEDITEM, item.getId()); }
values.put(KEY_FEED, item.getFeed().getId()); db.delete(TABLE_NAME_QUEUE, null, null);
db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE); for (int i = 0; i < queue.size(); i++) {
FeedItem item = queue.get(i);
values.put(KEY_ID, i);
values.put(KEY_FEEDITEM, item.getId());
values.put(KEY_FEED, item.getFeed().getId());
db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
} }
db.setTransactionSuccessful();
db.endTransaction();
} }
public void clearQueue() { public void clearQueue() {
@ -937,23 +1024,32 @@ public class PodDBAdapter {
* Remove a feed with all its FeedItems and Media entries. * Remove a feed with all its FeedItems and Media entries.
*/ */
public void removeFeed(Feed feed) { public void removeFeed(Feed feed) {
db.beginTransaction(); try {
if (feed.getImage() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
removeFeedImage(feed.getImage()); db.beginTransactionNonExclusive();
} } else {
if (feed.getItems() != null) { db.beginTransaction();
for (FeedItem item : feed.getItems()) {
removeFeedItem(item);
} }
} if (feed.getImage() != null) {
// delete download log entries for feed removeFeedImage(feed.getImage());
db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE +"=?", }
new String[] { String.valueOf(feed.getId()), String.valueOf(Feed.FEEDFILETYPE_FEED) }); if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
removeFeedItem(item);
}
}
// delete download log entries for feed
db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE + "=?",
new String[]{String.valueOf(feed.getId()), String.valueOf(Feed.FEEDFILETYPE_FEED)});
db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())}); new String[]{String.valueOf(feed.getId())});
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); } catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
db.endTransaction();
}
} }
public void clearPlaybackHistory() { public void clearPlaybackHistory() {