一.使用索引
創建索引的基本語法如下:
CREATE INDEX index_name ON table_name (column_name,...)
間接索引:
定義主鍵約束或者唯一性約束,可以間接創建索引,主鍵默認爲唯一約束.所 有不需要再創建索引,以免造成浪費.
注意事項:
1.建立索引會增加數據庫的大小.
2.對於insert,update,delete操作來說,使用索引會變慢,因爲同時需要維護索引的變化.
3.爲數據量較小的表建立索引,往往會事倍功半.
使用索引需要根據實際情況權衡利弊,對於查詢操作量級較大,查詢要求較高的推薦使用索引
使用場景
a. 當某字段數據更新頻率較低,查詢頻率較高,經常有範圍查詢(>, <, =, >=, <=)或order by、group by發生時建議使用索引。並且選擇度越大,建索引越有優勢,這裏選擇度指一個字段中唯一值的數量/總的數量。
b. 經常同時存取多列,且每列都含有重複值可考慮建立複合索引
索引使用規則
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. 把過濾記錄數最多的條件放在最前面
索引的運用時機:
1) 操作符:=、>、<、IN等
2) 操作符BETWEEN、LIKE、OR不能用索引,
如BETWEEN:SELECT * FROM mytable WHERE myfield BETWEEN 10 and 20;
這時就應該將其轉換成:
SELECT * FROM mytable WHERE myfield >= 10 AND myfield <= 20;
此時如果在myfield上有索引的話就可以用了,大大提高速度
再如LIKE:SELECT * FROM mytable WHERE myfield LIKE 'sql%';
此時應該將它轉換成:
SELECT * FROM mytable WHERE myfield >= 'sql' AND myfield < 'sqm';
此時如果在myfield上有索引的話就可以用了,大大提高速度
再如OR:SELECT * FROM mytable WHERE myfield = 'abc' OR myfield = 'xyz';
此時應該將它轉換成:
SELECT * FROM mytable WHERE myfield IN ('abc', 'xyz');
此時如果在myfield上有索引的話就可以用了,大大提高速度
二.編譯SQL語句
SQLite想要執行操作,需要將程序中的sql語句編譯成對應的SQLiteStatement,比如select * from record這一句,被執行100次就需要編譯100次。對於批量處理插入或者更新的操作,我們可以使用顯式編譯來做到重用SQLiteStatement。
想要做到重用SQLiteStatement也比較簡單,基本如下:
1.編譯sql語句獲得SQLiteStatement對象,參數使用?代替
2.在循環中對SQLiteStatement對象進行具體數據綁定,bind方法中的index 從1開始,不是0
請參考如下簡單的使用代碼:
private void insertWithPreCompiledStatement(SQLiteDatabase db) {
String sql = "INSERT INTO " + TableDefine.TABLE_RECORD + "( " + TableDefine.COLUMN_INSERT_TIME + ") VALUES(?)";
SQLiteStatement statement = db.compileStatement(sql);
int count = 0;
while (count < 100) {
count++;
statement.clearBindings();
statement.bindLong(1, System.currentTimeMillis());
statement.executeInsert();
}
}
三.顯式使用事務
在Android中,每次數據庫插入,更新操作都開啓了事務,確保操作的原子性.事務的實現需要藉助rollback journal文件,因此每次數據庫操作都會對文件進行打開讀寫關閉操作.
在批量的進行操作時,就會反覆的對文件進行打開讀寫關閉,降低執行的效率.此時,我們可以顯式的使用事務,將批量操作導致的文件操作次數變爲一次.
具體的實現代碼如下:
private void insertWithTransaction(SQLiteDatabase db) {
int count = 0;
ContentValues values = new ContentValues();
try {
db.beginTransaction();
while (count++ < 100) {
values.put(TableDefine.COLUMN_INSERT_TIME, System.currentTimeMillis());
db.insert(TableDefine.TABLE_RECORD, null, values);
}
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
}
}
四.查詢時返回更少的結果集及更少的字段
查詢時,查詢列的參數爲null時,默認查詢所有的列.如果你不需要查詢所有的列時,請將具體的列名傳入參數中,避免使用null,這樣可以提高查詢效率.
如果只需要返回一條數據時,可以添加limit限制,這樣找到一條結果後就不會再繼續查詢了.
五.避免使用Cursor.getColumnIndex()
Cursor.getColumnIndex()的時間差不多跟Cursor.getInt()相同,因此,避免使用它,尤其是在循環中.
使用常量來代替Cursor.getColumnIndex(),或者在循環外使用一次Cursor.getColumnIndex()來獲取index.
六.設定合適的ContentValues容量
ContentValues內部採用了HashMap來存儲數據,初始容量是8.當添加的數據超過該數值時,會進行擴容.因此可以爲ContentValues設置恰當的容量初始值,
避免內部頻繁進行擴容,尤其是在循環中使用的時候.
在使用其他List,Map等容器的時候,也可以參考此點.
七.及時關閉Cursor
八.查詢優化
影響查詢性能的因素:
1. 對錶中行的檢索數目,越小越好
2. 排序與否。
3. 是否要對一個索引。
4. 查詢語句的形式
幾個查詢優化的轉換
1. 對於單個表的單個列而言,如果都有形如T.C=expr這樣的子句,並且都是用OR操作符連接起來,
形如: x = expr1 OR expr2 = x OR x = expr3 此時由於對於OR,在SQLite中不能利用索引來優化,
所以可以將它轉換成帶有IN操作符的子句:
x IN(expr1,expr2,expr3)這樣就可以用索引進行優化,
效果很明顯,但是如果在都沒有索引的情況下OR語句執行效率會稍優於IN語句的效率。
2. 如果一個子句的操作符是BETWEEN,在SQLite中同樣不能用索引進行優化,所以也要進行相應的等價轉換:
如:a BETWEEN b AND c可以轉換成:(a BETWEEN b AND c) AND (a>=b) AND (a<=c)。
在上面這個子句中, (a>=b) AND (a<=c)將被設爲dynamic且是 (a BETWEEN b AND c)的子句,
那麼如果BETWEEN語句已經編碼,那麼子句就忽略不計,如果存在可利用的index使得子句已經滿足條件,那麼父句則被忽略。
3. 如果一個單元的操作符是LIKE,那麼將做下面的轉換:x LIKE ‘abc%’,轉換成:x>=‘abc’ AND x<‘abd’。
因爲在SQLite中的LIKE是不能用索引進行優化的,所以如果存在索引的話,則轉換後和不轉換相差很遠,因爲對LIKE不起作用,
但如果不存在索引,那麼LIKE在效率方面也還是比不上轉換後的效率的。
子查詢扁平化
例子:SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5
對這個SQL語句的執行一般默認的方法就是先執行內查詢,把結果放到一個臨時表中,再對這個表進行外部查詢,這就要對數據處理兩次,
另外這個臨時表沒有索引,所以對外部查詢就不能進行優化了,如果對上 面的SQL進行處理後可以得到如下SQL語句:
SELECT x+y AS a FROM t1 WHERE z<100 AND a>5,這個結果顯然和上面的一樣,但此時只需要對數據進行查詢一次就夠了,
另外如果在表t1上有索引的話就避免了遍歷整個表。
運用flatten方法優化SQL的條件:
1.子查詢和外查詢沒有都用集函數
2.子查詢沒有用集函數或者外查詢不是個表的連接
3.子查詢不是一個左外連接的右操作數
4.子查詢沒有用DISTINCT或者外查詢不是個表的連接
5.子查詢沒有用DISTINCT或者外查詢沒有用集函數
6.子查詢沒有用集函數或者外查詢沒有用關鍵字DISTINCT
7.子查詢有一個FROM語句
8.子查詢沒有用LIMIT或者外查詢不是表的連接
9.子查詢沒有用LIMIT或者外查詢沒有用集函數
10.子查詢沒有用集函數或者外查詢沒用LIMIT
11.子查詢和外查詢不是同時是ORDER BY子句
12.子查詢和外查詢沒有都用LIMIT
13.子查詢沒有用OFFSET
14.外查詢不是一個複合查詢的一部分或者子查詢沒有同時用關鍵字ORDER BY和LIMIT
15.外查詢沒有用集函數子查詢不包含ORDER BY
16.複合子查詢的扁平化:子查詢不是一個複合查詢,或者他是一個UNION ALL複合查詢,但他是都由若干個非集函數的查詢構 成,他的父查詢不是一個複合查詢的子查詢,也沒有用集函數或者是DISTINCT查詢,並且在FROM語句中沒有其它的表或者子查詢,父查詢和子查詢可能會包含WHERE語句,這些都會受到上面11、12、13條件的限制。
SELECT a+1 FROM (
SELECT x FROM tab
UNION ALL
SELECT y FROM tab
UNION ALL
SELECT abs(z*2) FROM tab2
) WHERE a!=5 ORDER BY 1
轉換爲:
SELECT x+1 FROM tab WHERE x+1!=5
UNION ALL
SELECT y+1 FROM tab WHERE y+1!=5
UNION ALL
SELECT abs(z*2)+1 FROM tab2 WHERE abs(z*2)+1!=5
ORDER BY 1
九.表數據拷貝到另外一張表中
INSERT INTO COMPANY_BKP
SELECT * FROM COMPANY
WHERE ID IN (SELECT ID
FROM COMPANY) ;
可以先將原表重命名,然後創建新的表,在將數據拷貝到新表中.
十.常用函數
SQLite 有許多內置函數用於處理字符串或數字數據。下面列出了一些有用的 SQLite 內置函數,且所有函數都是大小寫不敏感,這意味着您可以使用這些函數的小寫形式或大寫形式或混合形式:
序號 函數 & 描述
1 SQLite COUNT 函數
SQLite COUNT 聚集函數是用來計算一個數據庫表中的行數。
2 SQLite MAX 函數
SQLite MAX 聚合函數允許我們選擇某列的最大值。
3 SQLite MIN 函數
SQLite MIN 聚合函數允許我們選擇某列的最小值。
4 SQLite AVG 函數
SQLite AVG 聚合函數計算某列的平均值。
5 SQLite SUM 函數
SQLite SUM 聚合函數允許爲一個數值列計算總和。
6 SQLite RANDOM 函數
SQLite RANDOM 函數返回一個介於 -9223372036854775808 和 +9223372036854775807 之間的僞隨機整數。
7 SQLite ABS 函數
SQLite ABS 函數返回數值參數的絕對值。
8 SQLite UPPER 函數
SQLite UPPER 函數把字符串轉換爲大寫字母。
9 SQLite LOWER 函數
SQLite LOWER 函數把字符串轉換爲小寫字母。
10 SQLite LENGTH 函數
SQLite LENGTH 函數返回字符串的長度。
11 SQLite sqlite_version 函數
SQLite sqlite_version 函數返回 SQLite 庫的版本。