Android 數據庫優化

jopen 10年前發布 | 16K 次閱讀 Android Android開發 移動開發

本文為性能優化的第一篇——數據庫性能優化,原理適用于大部分數據庫包括Sqlite、Mysql、Oracle、Sql server,詳細介紹了索引(優缺點、分類、場景、規則)和事務,最后介紹了部分單獨針對Sqlite的優化。

1、索引

簡單的說,索引就像書本的目錄,目錄可以快速找到所在頁數,數據庫中索引可以幫助快速找到數據,而不用全表掃描,合適的索引可以大大提高數據庫查詢的效率。

(1). 優點
大大加快了數據庫檢索的速度,包括對單表查詢、連表查詢、分組查詢、排序查詢。經常是一到兩個數量級的性能提升,且隨著數據數量級增長。
 
(2). 缺點
索引的創建和維護存在消耗,索引會占用物理空間,且隨著數據量的增加而增加。
在對數據庫進行增刪改時需要維護索引,所以會對增刪改的性能存在影響。

(3). 分類

a. 直接創建索引和間接創建索引
直接創建: 使用sql語句創建,Android中可以在SQLiteOpenHelper的onCreate或是onUpgrade中直接excuSql創建語句,語句如:

CREATE INDEX mycolumn_index ON mytable (myclumn)
間接創建: 定義主鍵約束或者唯一性鍵約束,可以間接創建索引,主鍵默認為唯一索引。
 
b. 普通索引和唯一性索引
普通索引:
CREATE INDEX mycolumn_index ON mytable (myclumn)
唯一性索引:保證在索引列中的全部數據是唯一的,對聚簇索引和非聚簇索引都可以使用,語句為
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
 
c. 單個索引和復合索引
單個索引:索引建立語句中僅包含單個字段,如上面的普通索引和唯一性索引創建示例。
復合索引:又叫組合索引,在索引建立語句中同時包含多個字段,語句如:
CREATE INDEX name_index ON username(firstname, lastname)
其中firstname為前導列。
 
d. 聚簇索引和非聚簇索引(聚集索引,群集索引)
聚簇索引:物理索引,與基表的物理順序相同,數據值的順序總是按照順序排列,語句為:
CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH ALLOW_DUP_ROW
其中WITH ALLOW_DUP_ROW表示允許有重復記錄的聚簇索引
非聚簇索引:
CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)
索引默認為非聚簇索引

(4). 使用場景
在上面講到了優缺點,那么肯定會對何時使用索引既有點明白又有點糊涂吧,那么下面總結下:
a.  當某字段數據更新頻率較低,查詢頻率較高,經常有范圍查詢(>, <, =, >=, <=)或order by、group by發生時建議使用索引。并且選擇度越大,建索引越有優勢,這里選擇度指一個字段中唯一值的數量/總的數量。
b.  經常同時存取多列,且每列都含有重復值可考慮建立復合索引
 
(5). 索引使用規則
a.  對于復合索引,把使用最頻繁的列做為前導列(索引中第一個字段)。如果查詢時前導列不在查詢條件中則該復合索引不會被使用。
如create unique index PK_GRADE_CLASS on student (grade, class)
select * from student where class = 2未使用到索引
select * from dept where grade = 3使用到了索引
 
b.  避免對索引列進行計算,對where子句列的任何計算如果不能被編譯優化,都會導致查詢時索引失效
select * from student where tochar(grade)=’2′
c.  比較值避免使用NULL
d.  多表查詢時要注意是選擇合適的表做為內表。連接條件要充份考慮帶有索引的表、行數多的表,內外表的選擇可由公式:外層表中的匹配行數*內層表中每一次查找的次數確定,乘積最小為最佳方案。實際多表操作在被實際執行前,查詢優化器會根據連接條件,列出幾組可能的連接方案并從中找出系統開銷最小的最佳方案。
 e.  查詢列與索引列次序一致
f.  用多表連接代替EXISTS子句
g.  把過濾記錄數最多的條件放在最前面
h.  善于使用存儲過程,它使sql變得更加靈活和高效(Sqlite不支持存儲過程::>_<:: )
 
(6)索引檢驗
建立了索引,對于某條sql語句是否使用到了索引可以通過執行計劃查看是否用到了索引。

使用事務

使用事務的兩大好處是原子提交和更優性能。
(1) 原子提交
原則提交意味著同一事務內的所有修改要么都完成要么都不做,如果某個修改失敗,會自動回滾使得所有修改不生效。
 
(2) 更優性能
Sqlite默認會為每個插入、更新操作創建一個事務,并且在每次插入、更新后立即提交。
這樣如果連續插入100次數據實際是創建事務->執行語句->提交這個過程被重復執行了100次。如果我們顯示的創建事務->執行100條語句->提交會使得這個創建事務和提交這個過程只做一次,通過這種一次性事務可以使得性能大幅提升。尤其當數據庫位于sd卡時,時間上能節省兩個數量級左右。
Sqlte顯示使用事務,示例代碼如下:

public void insertWithOneTransaction() {
    SQLiteDatabase db = sqliteOpenHelper.getWritableDatabase();
    // Begins a transaction
    db.beginTransaction();
    try {
        // your sqls
        for (int i = 0; i < 100; i++) {
            db.insert(yourTableName, null, value);
        }

        // marks the current transaction as successful
        db.setTransactionSuccessful();
    } catch (Exception e) {
        // process it
        e.printStackTrace();
    } finally {
        // end a transaction
        db.endTransaction();
    }
}
其中sqliteOpenHelper.getWritableDatabase()表示得到寫表權限。

其他優化

(1) 語句的拼接使用StringBuilder代替String
這個就不多說了,簡單的string相加會導致創建多個臨時對象消耗性能。StringBuilder的空間預分配性能好得多。如果你對字符串的長度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,減少空間不夠時的再次分配。
 
(2) 讀寫表
在寫表時調用sqliteOpenHelper..getWritableDatabase(),在讀表時候調用sqliteOpenHelper..getReadableDatabase(),getReadableDatabase性能更優。
 
(3) 查詢時返回更少的結果集及更少的字段。
查詢時只取需要的字段和結果集,更多的結果集會消耗更多的時間及內存,更多的字段會導致更多的內存消耗。
 
(4) 少用cursor.getColumnIndex
根據性能調優過程中的觀察cursor.getColumnIndex的時間消耗跟cursor.getInt相差無幾。可以在建表的時候用static變量記住某列的index,直接調用相應index而不是每次查詢。

 

public static final String       HTTP_RESPONSE_TABLE_ID                  = android.provider.BaseColumns._ID;
public static final String       HTTP_RESPONSE_TABLE_RESPONSE            = "response";
public List<Object> getData() {
    ……
    cursor.getString(cursor.getColumnIndex(HTTP_RESPONSE_TABLE_RESPONSE));
    ……
}
優化為
public static final String       HTTP_RESPONSE_TABLE_ID                  = android.provider.BaseColumns._ID;
public static final String       HTTP_RESPONSE_TABLE_RESPONSE            = "response";
public static final int          HTTP_RESPONSE_TABLE_ID_INDEX            = 0;
public static final int          HTTP_RESPONSE_TABLE_URL_INDEX           = 1;
public List<Object> getData() {
    ……
    cursor.getString(HTTP_RESPONSE_TABLE_RESPONSE_INDEX);
    ……
}
異步線程

Sqlite是常用于嵌入式開發中的關系型數據庫,完全開源。
與Web常用的數據庫Mysql、Oracle db、sql server不同,Sqlite是一個內嵌式的數據庫,數據庫服務器就在你的程序中,無需網絡配置和管理,數據庫服務器端和客戶端運行在同一進程內,減少了網絡訪問的消耗,簡化了數據庫管理。不過Sqlite在并發、數據庫大小、網絡方面存在局限性,并且為表級鎖,所以也沒必要多線程操作。
 
Android中數據不多時表查詢可能耗時不多,不會導致anr,不過大于100ms時同樣會讓用戶感覺到延時和卡頓,可以放在線程中運行,但sqlite在并發方面存在局限,多線程控制較麻煩,這時候可使用單線程池,在任務中執行db操作,通過handler返回結果和ui線程交互,既不會影響UI線程,同時也能防止并發帶來的異常。實例代碼如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(new Runnable() {

    @Override
    public void run() {
        // db operetions, u can use handler to send message after
        db.insert(yourTableName, null, value);
        handler.sendEmptyMessage(xx);
    }
});

 

 

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!