隨著app信息量越來越大,每次從網絡獲取數據已經不是很可取的方案了,本地數據庫的運用已經越來越普遍了。
而說道移動端,不得不提的就是SQlite了,隨著本地數據庫的大量運用,FMDB也受到了很多程序員的關注。
FMDB是一個很棒的庫,對SQLite的進行了一層更符合mvc的包裝,使得使用變得非常便捷和方便。
回過頭來說,移動DB的數據量也是越來越大,動輒10萬條的數據已經不再稀奇,對于幾十萬條大量的數據來說,讀取,插入等的優化對于用戶體驗的優化是很重要的。
優化
對于沒有做任何優化的DB來說,幾十萬的數據讀取,至少是幾十秒甚至可能需要更長的時間,即使使用loading頁面等的緩沖,這還是遠遠不夠的。
1)使用數據庫事務
FMDB默認是對每一句sqlite語句執行事務操作的。事務是為了對數據庫進行操作失敗時,進行數據回滾的一種安全機制。但是沒次執行都執行事務是一件非常耗時的操作,此時在進行大量數據操作時,可以在全部語句執行完成后,在做全面的事務操作。例:
-(void)shiwucaozuo
{
[_dataBase inTransaction:^(FMDatabase *db, BOOL *rollback) {
for (int i = 0; i<100000; i++) {
//執行sql語句
BOOL a = [db executeUpdate:SQL];
if (!a) {
*rollback = YES;
return;
}
}
}];
}
這樣顯示的控制就可以有效的控制時間了,插入等操作優化會有著一個數量級的增長優化。
小技巧:在查詢出數據后,在數據量不大的情況下,可以加載到內存中,避免每次進行數據庫查詢操作。
比如,數據轉換成model以后可以以key-value的形式存儲在字典中
2)使用FMDB的Statement cache的機制
FMDB的cache機制就是在執行DB操作結束后會根據sql作為key查詢cache。如果沒有cache就會加入cache中,會有兩次查詢操作。可以利用FMDB的cache緩存sql,這里推薦的做法是緩存不帶參數的sql,然后在準備執行使用時直接通過緩存時返回的id從cache中直接獲取sql語句,這樣只需要一次的查詢操作,在執行大量sql語句時,大量的查詢還是會對性能有所損耗的,只緩存不帶參數的sql語句有助于提高查詢效率
例:
-(void)cacheSQl:(FMDatabase*) withSql:(NSString*)sql{
id = [db cache:sql]'
//上面的db需要緩存,下次可以通過該id找回sql.
}
可以通過
[db executeUpdataWithStatementID]方法直接獲取sql。
3)修改日志模式
日志模式
SQLite中日志模式主要有DELETE和WAL兩種,其他幾種比如TRUNCATE,PERSIST,MEMORY基本原理都與DELETE模式相同,不作詳細展開。DELETE模式采用影子分頁技術(Shadow paging),DELETE模式下,日志中記錄的變更前數據頁內容;WAL模式下,日志中記錄的是變更后的數據頁內容。事務提交時,DELETE模式將日志刷盤,將DB文件刷盤,成功后,再將日志文件清理;WAL模式則是將日志文件刷盤,即可完成提交過程。那么WAL模式下,數據文件何時更新呢?這里引入了檢查點概念,檢查點的作用就是定期將日志中的新頁覆蓋DB文件中的老頁,并通過參數wal_autocheckpoint來控制檢查點時機,達到權衡讀寫的目的。
DELETE模式下,寫事務直接更新db-page,并將old-page寫入日志,讀事務則直接讀db-page,因為db-page中保存了提交的所有事務的更新。事務提交后,直接將日志文件刪除;若事務需要回滾,則將日志中old-page中的內容覆蓋db-page,恢復原始內容。WAL模式下,寫事務將更新寫到日志文件中,不更新db-page,事務提交時,也不影響db-page,只是將日志持久化而已。若事務回滾,則不將日志寫入文件即可。由于最新的數據在日志文件中,那么如何讀取到最新的數據呢?WAL模式通過end-mark(事務提交位點)達到這一目的。具體而已,事務開始時,會首先掃描日志文件,獲取最近一個end-mark,在讀取數據時,首先會判斷page是否則在wal日志文件中存在,因為同一個page,一定是wal文件中的比db文件中的要新。如果存在,則使用,否則,再從db文件中獲取指定的page。從流程上來看,這個過程比較慢,因為極端情況下,每次讀都需要掃描wal文件和db文件。為了提高性能,WAL模式中有一個wal-index文件,這個文件記錄了頁號和該頁在WAL文件中的偏移,并且wal-index文件采用共享緩存實現,從文件名也可以看到,后綴是.shm,因此判斷page是否在wal文件存在的操作實質是一次內存讀。wal-index采用hash表存儲,因此查詢效率也非常高。與傳統的DBMS不同,SQLite中記錄的日志,實質是dirty-page,重做實質是對利用WAL中的日志頁覆蓋db-page,這種實現方式比較簡單,同時也比較浪費空間,因為一個page是1k,即使只更新1byte,也會導致日志記錄1k。
我們可以在數據庫建立時就改變日志模式
WAL日志模式優點:
1) 讀寫可以并發,不會阻塞
2)只有一個WAL文件,性能優勢明顯。
4)調整數據變索引建立時間。
在創建表時,很多時候需要建立索引,索引可以提升搜索的效率,但是建立索引尤其是插入大量數據重新建立索引時需要耗費大量的性能損耗。所以可以可以在全量拉取數據完成后手動添加index索引。
5)寫同步(synchronous)(不推薦)
在SQLite中,數據庫配置的參數都由編譯指示(pragma)來實現的,而其中synchronous選項有三種可選狀態,分別是full、normal、off。這篇博客以及官方文檔里面有詳細講到這三種參數的設置。簡要說來,full寫入速度最慢,但保證數據是安全的,不受斷電、系統崩潰等影響,而off可以加速數據庫的一些操作,但如果系統崩潰或斷電,則數據庫可能會損毀。
SQLite3中,該選項的默認值就是full,如果我們再插入數據前將其改為off,則會提高效率。如果僅僅將SQLite當做一種臨時數據庫的話,完全沒必要設置為full。在代碼中,設置方法就是在打開數據庫之后,直接插入以下語句:
sqlite3_exec(db,"PRAGMA synchronous = OFF; ",0,0,0);
當synchronous設置為FULL (2), SQLite數據庫引擎在緊急時刻會暫停以確定數據已經寫入磁盤。這使系統崩潰或電源出問題時能確保數據庫在重起后不會損壞。FULL synchronous很安全但很慢。
當synchronous設置為NORMAL, SQLite數據庫引擎在大部分緊急時刻會暫停,但不像FULL模式下那么頻繁。 NORMAL模式下有很小的幾率(但不是不存在)發生電源故障導致數據庫損壞的情況。但實際上,在這種情況 下很可能你的硬盤已經不能使用,或者發生了其他的不可恢復的硬件錯誤。
設置為synchronous OFF (0)時,SQLite在傳遞數據給系統以后直接繼續而不暫停。若運行SQLite的應用程序崩潰, 數據不會損傷,但在系統崩潰或寫入數據時意外斷電的情況下數據庫可能會損壞。另一方面,在synchronous OFF時 一些操作可能會快50倍甚至更多。但是這種操作還是有風險的。
6)關于查詢的優化FTS
FTS虛擬表對于查詢的優化是很明顯的。
FTS3 和FTS4 是一個SQLite 虛擬表的模塊, 允許用戶執行全文搜索一組文檔從最常見()方法
但是在使用過程中發現有很多不一致的地方 如:
CREATE VIRTUAL TABLE table1 USING fts4(content TEXT) */ FTS4 表/*
CREATE TABLE IF NOT EXISTS table1(content TEXT); /* 普通表*/
當然在FMDB中:
NSString *storePath = @"db路徑";
FMDatabase *db = [FMDatabase databaseWithPath:storePath];
[db open];
FMSimpleTokenizer *simpleTok = [[FMSimpleTokenizer alloc] initWithLocale:NULL];
[db installTokenizerModule];
[FMDatabase registerTokenizer:simpleTok withKey:@"simple"];
[db executeUpdate:@"CREATE VIRTUAL TABLE works_test USING fts4(id, title, title_tr, content, content_tr, dynasty, dynasty_tr, author, author_tr, tokenize=fmdb simple)"];
[db close];
插入等其他操作和原來一樣。
但是查詢的時候不是我們通常喜歡使用的#like#了 而是 #MATCH# 當然據說比like查詢的速度快上1000倍(聽說)
SELECT * FROM works_test WHERE works_test MATCH 'something';
7)最后關于PRAGMA命令用法
PRAGMA語句是SQLITE數據的SQL擴展,是它獨有的特性,主要用于修改SQLITE庫或者內數據查詢的操作。它采用與SELECT、INSERT等語句一樣的形式來發出請求,但也有幾個重要的不同:
- 特定的PRAGMA語句可能被移走,新的PRAGMA語句可能在新的版本中添加。因此,后向兼容無法保證。
- 未知的PRAGMA命令不會有錯誤消息出現,它只是簡單的忽略。
- 有些PRAGMA只在SQL的編譯階段起作用,而不是執行階段。 這意味著如果使用C語言,sqlite3_prepare(), sqlite3_step(), sqlite3_finalize()這幾個API,pragma命令可能只在prepare()的調用里運行,而不是在后兩個API當中執行。或者,pragma可能在sqlite3_step()執行的時候運行。到底在哪個階段執行,取決于pragma從本身,以及是哪個sqlite的release版本。
- pragma命令是sqlite特有的,基本上不可能與其它數據庫保持兼容。
下面我們看看sqlite到底有些有用的pragma命令:
auto_vacuum
automatic_index
cache_size
case_sensitive_like
checkpoint_fullfsync
collation_list
compile_options
count_changes1
database_list
default_cache_size1
empty_result_callbacks1
encoding
foreign_key_list
foreign_keys
freelist_count
full_column_names1
fullfsync
ignore_check_constraints
incremental_vacuum
index_info
index_list
integrity_check
journal_mode
journal_size_limit
legacy_file_format
locking_mode
max_page_count
page_count
page_size
parser_trace2
quick_check
read_uncommitted
recursive_triggers
reverse_unordered_selects
schema_version
secure_delete
short_column_names1
synchronous
table_info
temp_store
temp_store_directory1
user_version
vdbe_listing2
vdbe_trace2
wal_autocheckpoint
wal_checkpoint
writable_schema
這里邊有幾個標了右上標為1的,似乎已經被obsoleted掉了。標為2的,只被用于debug,僅當sqlite在預編譯宏SQLITE_DEBUG下build出來,才有用。
下面看看這些命令的具體用法:
PRAGMA auto_vacuum;
PRAGMA auto_vacuum = 0 或 NONE | 1 或 FULL | 2 或 INCREMENTAL;
這里,0和NONE表示的含義相同。
缺省值為0, 表示禁用auto vacuum. 除非SQLITE_DEFAULT_AUTOVACUUM宏在編譯的時候定義了。數據刪除的時候,數據庫大小不會改變。沒用的數據庫文件頁面會被添加到freelist里頭,用于將來重用。這時,使用VACUUM命令,可以重建整個數據庫,以回收無用的磁盤空間。
值為1時,所有的freelist頁會被移動到文件末尾,每次事務提交的時候文件會被截短。注意,自動vacuum只是從文件是截斷freelist頁,并沒有進行碎片重整等操作,也就是說,它沒有VACUUM命令來得徹底。事實上,自動vacuum會讓碎片更多。
只有在數據庫存儲某些附加信息的時候,它允許每個數據庫頁來跟蹤它的引用頁,自動vacuum才用得上。它必須在沒有創建任何表的情況下啟用。在一個表已經創建了之后,是不能啟用和停用auto-vacuum的。
值為2時,表示增量vacuum,意味著并不是在每次提交事務的時候自動vacuum,需要調用一個獨立的incremental_vacuum語句來觸發auto-vacuum。
數據庫可以在1和2兩種vacuum模式下進行切換。但是不能從none到full或incremental間切換。要想切換,要么數據庫是全新的數據庫(沒有任何表), 或者單獨運行vacuum命令以后。改變自動vacuum模式,首先執行auto_vacuum語句設置新的模式,然后調用VACUUM來重整數據庫。
不帶參數的auto_vacuum語句返回當前的auto_vacuum模式值。PRAGMA automatic_index;
PRAGMA automatic_index = boolean;
查詢,設置或者清除自動索引的功能。缺省值為true (1).PRAGMA cache_size;
PRAGMA cache_size = <number of pages>;
查詢或者修改打開的數據庫內存里頭能容納的最多的數據庫頁數。缺省值是2000. 這樣設定只會改變當前會話中的cache size,當數據庫重新打開,又會恢復默認值。你可以使用default_cache_size來設定所有會話中的cache sizePRAGMA case_sensitive_like=boolean;
默認行為是忽略ascii字符的大小寫。'a' LIKE 'A'會是true. 當禁用case_sensitive_like時,會用默認的like行為。當啟用它時,就會區分大小寫。PRAGMA checkpoint_fullfsync
PRAGMA checkpoint_fullfsync=boolean;
查詢或設置fullfsync的標志值。如果設置了該值,則F_FULLFSYNC同步方法會在checkpoint操作時調用,默認值是off。只有Mac OS-X操作系統支持F_FULLFSYNC。另外,如果設定了fullfsync值,那么F_FULLFSYNC同步方法會在所有sync操作里使用,也checkpoint_fullfsync標志完全無關。PRAGMA collation_list;
返回當前數據庫連接定義的所有排序順序。PRAGMA compile_options;
這個要贊,返回編譯SQLITE時使用的所有預編譯宏。當然,以"SQLITE_"打頭的前綴會被忽略。實際上它是通過調用sqlite3_compileoption_get()方法返回的。PRAGMA count_changes;
PRAGMA count_changes=boolean;
該命令已經停用. 只是為了保持后向兼容. 如果不設置此值,INSERT, UPDATE, DELETE語句不會返回多少行改變的數據。
事實上,sqlite3_changes()可以獲取改變的行數。PRAGMA database_list;
返回當前數據庫連接關聯的數據庫列表.PRAGMA default_cache_size;
PRAGMA default_cache_size = Number-of-pages;
設置缺省的cache sie, 是以頁為單位。不幸的是,該命令也將被廢棄。PRAGMA empty_result_callbacks;
PRAGMA empty_result_callbacks = boolean;
僅作后向兼容用。如果將該標志值清除,sqlite3_exec()提供的回調函數(返回0或多行數據)將不被觸發。PRAGMA encoding;
PRAGMA encoding = "UTF-8";
PRAGMA encoding = "UTF-16";
PRAGMA encoding = "UTF-16le";
PRAGMA encoding = "UTF-16be";
缺省值是utf-8。如果使用attach命令,則會要求使用與main數據庫相同的字符集編碼,如果新的數據庫編碼與main不同,則會失敗。PRAGMA foreign_key_list(table-name);
返回外鍵列表PRAGMA foreign_keys;
PRAGMA foreign_keys = boolean;
查詢設置或者清除關于外鍵的限制, 外鍵限制只有在BEGIN或者SAVEPOINT不在PENDING狀態時設置才有效。
改變該設置會影響所有已經準備好的SQL語句的執行。
從3.6.19開始,默認的FK強制限制是OFF。也就是說,不會強制外鍵依賴。PRAGMA freelist_count;
返回數據庫文件中未使用頁的數目-
PRAGMA full_column_names;
PRAGMA full_column_names = boolean;
deprecated.- 如果有AS子句,列名就會用AS后的別名
- 如果結果只是普通的表達式,而不是源表的列名,則采用表達式的文本
- 如果使用了short_column_names開關為ON,則采用源表列名,并且不帶表名前綴
- 如果兩個開關都設為OFF,則采用第2個規則。
- 結果列是學有源表源列的組合:TABLE.COLUMN
PRAGMA fullfsync;
PRAGMA fullfsync = boolean;
缺省值為OFF,也只有MAC os支持F_FULLFSYNCPRAGMA ignore_check_constraints = boolean;
是否強制check約束,缺省值為offPRAGMA incremental_vacuum(N);
N頁從freelist中移除。用于設定此參數。每次截短相同的頁數。該命令必須是在auto_vacuum=incremental模式下才有效。如果freelist中的頁數少于N,或者N小于1,或者N被完全忽略,那么整個freelist會被清除。PRAGMA index_info(index-name);
獲取具名的index信息。PRAGMA index_list(table-name);
獲取與目標表關聯的索引的的相關信息PRAGMA integrity_check;
PRAGMA integrity_check(integer);
執行整個庫的完全性檢查,會查看錯序的記錄、丟失的頁,毀壞的索引等。
PRAGMA journal_mode;
PRAGMA database.journal_mode;
PRAGMA journal_mode = DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
PRAGMA database.journal_mode = DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
用于設置數據庫的journal_mode. DELETE是缺省的行為。在此模式下,每次事務終止的時候,journal文件會被刪除,它會導致事務提交。
TRUNCATE模式,通過將回滾journal截短成0,而不是刪除它。大多數情情況下,它要比DELETE模式速度快(因為不用刪除文件)
PERSIST模式,每次事務結束時,并不刪除rollback journal,而只是在journal的頭部填充0,這樣會阻止別的數據庫連接來rollback. 該模式在某些平臺下,是一種優化,特別是刪除或者truncate一個文件比覆蓋文件的第一塊代價高的時候。
MEMORY模式,只將rollback日志存儲到RAM中,節省了磁盤I/O,但帶來的代價是穩定性和完整性上的損失。如果中間crash掉了,數據庫有可能損壞。
WAL模式,也就是write-ahead log取代rollback journal。該模式是持久化的,跨多個數據為連接,在重新打開數據庫以后,仍然有效。該模式只在3.7.0以后才有效。
(經過實驗,發現,它會生成兩個文件:.shm和.wal)
OFF模式,這樣就沒有事務支持了。
另外要注意的是,對于memory數據庫,只有兩種模式: MEMORY或者OFF。并且,當前如果有活躍的事務,則不允許改變事務模式。
PRAGMA journal_size_limit
PRAGMA journal_size_limit = N ;
如果連接時,用了"exclusive mode(PRAGMA locking_mode=exclusive)或者(PRAGMA journal_mode=persist), 提交事務以后,journal文件會仍然在文件系系統當中。這可能會提高了效率,但是也損耗了空間。一個大的事務(如VACUUM),會耗費大量的磁盤空間。
該設置會限制journal文件的大小。默認值是-1。PRAGMA legacy_file_format;
PRAGMA legacy_file_format = boolean;
如果該值為ON,則會采用3.0.0文件格式,如果為off, 則會采用最新的文件格式,可能導致舊版本的sqlite無法打開該文件。
第一次新文件格式的sqlite3數據庫打開時,該值為off.但是默認值會是on.-
PRAGMA locking_mode;
PRAGMA locking_mode = NORMAL | EXCLUSIVE
缺省值是NORMAL. 數據庫連接在每一個讀或寫事務終點的時候放掉文件鎖。如果是EXCLUSIVE模式,連接永遠不會釋放文件鎖。在此模式下,第一次執行讀操作時,會獲取并持有共享鎖,第一次寫,會獲取并持有排它鎖。
釋放排它鎖,僅當關閉數據庫連接,或者將鎖模式改回為NORMAL時,再次訪問數據庫文件(讀或寫)才會放掉。簡單的設置為NORMAL是不夠的,只有當下次再訪問時才會釋放排它鎖。
有下述三個理由,去設置鎖模式為EXCLUSIVE- 應用程序需要阻止其它進程訪問數據庫文件
- 文件系統的系統調用數量減少了,導致些許性能下降
- WAL日志模式可以在EXCLUSIVE模式下使用,而不需要用到共享內存
當指定數據庫名時,只能目標數據庫生效。如:
PRAGMA main.locking_mode=EXCLUSIVE; 不指定數據庫名時,則對所有打開的數據庫生效。temp或者memory數據庫總是使用exclusive鎖模式。
第一次進入WAL日志模式時,鎖模式使用的是exclusive,這以后,鎖模式也不能改變,直到退出WAL日志模式,如果鎖模式開始時使用的是NORMAL,第一次進入WAL,這時鎖模式可以改變,并且不需要退出WAL模式。
PRAGMA max_page_count;
PRAGMA max_page_count = N;
查詢或者設置數據庫文件的最大頁數PRAGMA page_count;
返回數據庫文件的頁數PRAGMA page_size;
PRAGMA page_size = bytes;
查詢或者設置數據庫文件的頁大小, 必須是2的乘方,并且介于512和65536之間。
創建數據庫時,會給定一個缺省的大小。page_size命令會立即改變頁大小(如果數據庫是空的話,就是說在沒有創建任何表的情況下)。如果指定了新大小,是在運行VACUUM命令之間,同時數據庫不是在WAL日志模式下,那么VACUUM命令會將頁大小調整到新的大小(這時應該沒有是事創建表的限制)
SQLITE_DEFAULT_PAGE_SIZE 缺省值是1024,最大的缺省頁大小是8192. windows下,有時候可能缺省頁大小大于1024,取決于GetDiskFreeSpace()來獲取真實的設置扇區大小。PRAGMA parser_trace = boolean;
用在DEBUG的時候。PRAGMA quick_check;
PRAGMA quick_check(integer)
與integrity_check相像,但是略去了對索引內容與表內容匹配的校驗。PRAGMA read_uncommitted;
PRAGMA read_uncommitted = boolean;
讀未提交開關。缺省的事務隔離級是:可串行化。任何進程或線程都可以設置讀未提交隔離級,但是,SERIALIZABLE仍被使用,除了共享某頁和表模式的緩存的那些連接。PRAGMA recursive_triggers;
PRAGMA recursive_triggers = boolean;
會影響所有的語句執行。3.6.18以前,這個開關是不支持的。缺省值是off.PRAGMA reverse_unordered_selects;
PRAGMA reverse_unordered_selects = boolean;
當開啟此開關時,不帶order by的select語句,會輸出相反順序的結果。PRAGMA schema_version;
PRAGMA schema_version = integer ;
PRAGMA user_version;
PRAGMA user_version = integer ;
schema和user version是在數據庫文件頭40,60字節處的32位整數(大端表示)。
schema版本由sqlite內部維護,當schema改變時,就會增加該值。顯式改變該值非常危險。
user版本可以被應用程序使用。PRAGMA secure_delete;
PRAGMA database.secure_delete;
PRAGMA secure_delete = boolean
PRAGMA database.secure_delete = boolean
設為ON時,刪除的內容會用0來覆蓋。缺省值由宏SQLITE_SECURE_DELETE 決定。那就是OFF了。PRAGMA short_column_names;
PRAGMA short_column_names = boolean;
deprecated.PRAGMA synchronous;
PRAGMA synchronous = 0 | OFF | 1 | NORMAL | 2 | FULL;
查詢設置sync標志值。缺省值是FULL.PRAGMA table_info(table-name);
返回表的基本信息PRAGMA temp_store;
PRAGMA temp_store = 0 | DEFAULT | 1 | FILE | 2 | MEMORY;
查詢或設置temp_store參數值。
SQLITE_TEMP_STORE PRAGMA temp_store Storage used forTEMP tables
0 any file
1 0 file
1 1 file
1 2 memory
2 0 memory
2 1 file
2 2 memory
3 any memoryPRAGMA temp_store_directory;
PRAGMA temp_store_directory = 'directory-name';
設置或改變temp_store的目錄位置. deprecated.PRAGMA vdbe_listing = boolean;
用于DEBUGPRAGMA vdbe_trace = boolean;
用于DEBUGPRAGMA wal_autocheckpoint;
PRAGMA wal_autocheckpoint=N;
設置WAL自動檢查點的間隔(以頁為單位), 缺省值是1000。PRAGMA database.wal_checkpoint;
PRAGMA database.wal_checkpoint(PASSIVE);
PRAGMA database.wal_checkpoint(FULL);
PRAGMA database.wal_checkpoint(RESTART);PRAGMA writable_schema = boolean;
當設為ON時,SQLITE_MASTER表可以執行CUD操作。這樣做很危險!!