關於ios本地大容量儲存sqlite優化

weixin_33686714發表於2017-12-14

隨著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的形式儲存在字典中,這樣下次使用時會更快減少db操作

####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等語句一樣的形式來發出請求,但也有幾個重要的不同:

  1. 特定的PRAGMA語句可能被移走,新的PRAGMA語句可能在新的版本中新增。因此,後向相容無法保證。
  2. 未知的PRAGMA命令不會有錯誤訊息出現,它只是簡單的忽略。
  3. 有些PRAGMA只在SQL的編譯階段起作用,而不是執行階段。 這意味著如果使用C語言,sqlite3_prepare(), sqlite3_step(), sqlite3_finalize()這幾個API,pragma命令可能只在prepare()的呼叫裡執行,而不是在後兩個API當中執行。或者,pragma可能在sqlite3_step()執行的時候執行。到底在哪個階段執行,取決於pragma從本身,以及是哪個sqlite的release版本。
  4. pragma命令是sqlite特有的,基本上不可能與其它資料庫保持相容。

下面我們看看sqlite到底有些有用的pragma命令: auto_vacuum automatic_index cache_size case_sensitive_like checkpoint_fullfsync collation_list compile_options count_changes¹ database_list default_cache_size¹ empty_result_callbacks¹ encoding foreign_key_list foreign_keys freelist_count full_column_names¹ 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_trace² quick_check read_uncommitted recursive_triggers reverse_unordered_selects schema_version secure_delete short_column_names¹ synchronous table_info temp_store temp_store_directory¹ user_version vdbe_listing² vdbe_trace² wal_autocheckpoint wal_checkpoint writable_schema 這裡邊有幾個標了右上標為1的,似乎已經被obsoleted掉了。標為2的,只被用於debug,僅當sqlite在預編譯巨集SQLITE_DEBUG下build出來,才有用。

下面看看這些命令的具體用法:

  1. 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模式值。

  2. PRAGMA automatic_index; PRAGMA automatic_index = boolean; 查詢,設定或者清除自動索引的功能。預設值為true (1).

  3. PRAGMA cache_size; PRAGMA cache_size = ; 查詢或者修改開啟的資料庫記憶體裡頭能容納的最多的資料庫頁數。預設值是2000. 這樣設定只會改變當前會話中的cache size,當資料庫重新開啟,又會恢復預設值。你可以使用default_cache_size來設定所有會話中的cache size

  4. PRAGMA case_sensitive_like=boolean; 預設行為是忽略ascii字元的大小寫。'a' LIKE 'A'會是true. 當禁用case_sensitive_like時,會用預設的like行為。當啟用它時,就會區分大小寫。

  5. PRAGMA checkpoint_fullfsync PRAGMA checkpoint_fullfsync=boolean; 查詢或設定fullfsync的標誌值。如果設定了該值,則F_FULLFSYNC同步方法會在checkpoint操作時呼叫,預設值是off。只有Mac OS-X作業系統支援F_FULLFSYNC。另外,如果設定了fullfsync值,那麼F_FULLFSYNC同步方法會在所有sync操作裡使用,也checkpoint_fullfsync標誌完全無關。

  6. PRAGMA collation_list; 返回當前資料庫連線定義的所有排序順序。

  7. PRAGMA compile_options; 這個要贊,返回編譯SQLITE時使用的所有預編譯巨集。當然,以"SQLITE_"打頭的字首會被忽略。實際上它是通過呼叫sqlite3_compileoption_get()方法返回的。

  8. PRAGMA count_changes; PRAGMA count_changes=boolean; 該命令已經停用. 只是為了保持後向相容. 如果不設定此值,INSERT, UPDATE, DELETE語句不會返回多少行改變的資料。 事實上,sqlite3_changes()可以獲取改變的行數。

  9. PRAGMA database_list; 返回當前資料庫連線關聯的資料庫列表.

  10. PRAGMA default_cache_size; PRAGMA default_cache_size = Number-of-pages; 設定預設的cache sie, 是以頁為單位。不幸的是,該命令也將被廢棄。

  11. PRAGMA empty_result_callbacks; PRAGMA empty_result_callbacks = boolean; 僅作後向相容用。如果將該標誌值清除,sqlite3_exec()提供的回撥函式(返回0或多行資料)將不被觸發。

  12. PRAGMA encoding; PRAGMA encoding = "UTF-8"; PRAGMA encoding = "UTF-16"; PRAGMA encoding = "UTF-16le"; PRAGMA encoding = "UTF-16be"; 預設值是utf-8。如果使用attach命令,則會要求使用與main資料庫相同的字符集編碼,如果新的資料庫編碼與main不同,則會失敗。

  13. PRAGMA foreign_key_list(table-name); 返回外來鍵列表

  14. PRAGMA foreign_keys; PRAGMA foreign_keys = boolean; 查詢設定或者清除關於外來鍵的限制, 外來鍵限制只有在BEGIN或者SAVEPOINT不在PENDING狀態時設定才有效。   改變該設定會影響所有已經準備好的SQL語句的執行。 從3.6.19開始,預設的FK強制限制是OFF。也就是說,不會強制外來鍵依賴。

  15. PRAGMA freelist_count; 返回資料庫檔案中未使用頁的數目

  16. PRAGMA full_column_names; PRAGMA full_column_names = boolean; deprecated.

    1. 如果有AS子句,列名就會用AS後的別名
    2. 如果結果只是普通的表示式,而不是源表的列名,則採用表示式的文字
    3. 如果使用了short_column_names開關為ON,則採用源表列名,並且不帶表名字首
    4. 如果兩個開關都設為OFF,則採用第2個規則。
    5. 結果列是學有源表源列的組合:TABLE.COLUMN
  17. PRAGMA fullfsync; PRAGMA fullfsync = boolean; 預設值為OFF,也只有MAC os支援F_FULLFSYNC

  18. PRAGMA ignore_check_constraints = boolean; 是否強制check約束,預設值為off

  19. PRAGMA incremental_vacuum(N); N頁從freelist中移除。用於設定此引數。每次截短相同的頁數。該命令必須是在auto_vacuum=incremental模式下才有效。如果freelist中的頁數少於N,或者N小於1,或者N被完全忽略,那麼整個freelist會被清除。

  20. PRAGMA index_info(index-name); 獲取具名的index資訊。

  21. PRAGMA index_list(table-name); 獲取與目標表關聯的索引的的相關資訊

  22. 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。並且,當前如果有活躍的事務,則不允許改變事務模式。

  1. PRAGMA journal_size_limit PRAGMA journal_size_limit = N ; 如果連線時,用了"exclusive mode(PRAGMA locking_mode=exclusive)或者(PRAGMA journal_mode=persist), 提交事務以後,journal檔案會仍然在檔案繫系統當中。這可能會提高了效率,但是也損耗了空間。一個大的事務(如VACUUM),會耗費大量的磁碟空間。 該設定會限制journal檔案的大小。預設值是-1。

  2. PRAGMA legacy_file_format; PRAGMA legacy_file_format = boolean; 如果該值為ON,則會採用3.0.0檔案格式,如果為off, 則會採用最新的檔案格式,可能導致舊版本的sqlite無法開啟該檔案。 第一次新檔案格式的sqlite3資料庫開啟時,該值為off.但是預設值會是on.

  3. PRAGMA locking_mode; PRAGMA locking_mode = NORMAL | EXCLUSIVE 預設值是NORMAL. 資料庫連線在每一個讀或寫事務終點的時候放掉檔案鎖。如果是EXCLUSIVE模式,連線永遠不會釋放檔案鎖。在此模式下,第一次執行讀操作時,會獲取並持有共享鎖,第一次寫,會獲取並持有排它鎖。 釋放排它鎖,僅當關閉資料庫連線,或者將鎖模式改回為NORMAL時,再次訪問資料庫檔案(讀或寫)才會放掉。簡單的設定為NORMAL是不夠的,只有當下次再訪問時才會釋放排它鎖。 有下述三個理由,去設定鎖模式為EXCLUSIVE

    1. 應用程式需要阻止其它程式訪問資料庫檔案
    2. 檔案系統的系統呼叫數量減少了,導致些許效能下降
    3. WAL日誌模式可以在EXCLUSIVE模式下使用,而不需要用到共享記憶體 當指定資料庫名時,只能目標資料庫生效。如: PRAGMA main.locking_mode=EXCLUSIVE; 不指定資料庫名時,則對所有開啟的資料庫生效。temp或者memory資料庫總是使用exclusive鎖模式。   第一次進入WAL日誌模式時,鎖模式使用的是exclusive,這以後,鎖模式也不能改變,直到退出WAL日誌模式,如果鎖模式開始時使用的是NORMAL,第一次進入WAL,這時鎖模式可以改變,並且不需要退出WAL模式。
  4. PRAGMA max_page_count; PRAGMA max_page_count = N; 查詢或者設定資料庫檔案的最大頁數

  5. PRAGMA page_count; 返回資料庫檔案的頁數

  6. PRAGMA page_size; PRAGMA page_size = bytes; 查詢或者設定資料庫檔案的頁大小, 必須是2的乘方,並且介於512和65536之間。 建立資料庫時,會給定一個預設的大小。page_size命令會立即改變頁大小(如果資料庫是空的話,就是說在沒有建立任何表的情況下)。如果指定了新大小,是在執行VACUUM命令之間,同時資料庫不是在WAL日誌模式下,那麼VACUUM命令會將頁大小調整到新的大小(這時應該沒有是事建立表的限制) SQLITE_DEFAULT_PAGE_SIZE 預設值是1024,最大的預設頁大小是8192. windows下,有時候可能預設頁大小大於1024,取決於GetDiskFreeSpace()來獲取真實的設定扇區大小。

  7. PRAGMA parser_trace = boolean; 用在DEBUG的時候。

  8. PRAGMA quick_check; PRAGMA quick_check(integer) 與integrity_check相像,但是略去了對索引內容與表內容匹配的校驗。

  9. PRAGMA read_uncommitted; PRAGMA read_uncommitted = boolean; 讀未提交開關。預設的事務隔離級是:可序列化。任何程式或執行緒都可以設定讀未提交隔離級,但是,SERIALIZABLE仍被使用,除了共享某頁和表模式的快取的那些連線。

  10. PRAGMA recursive_triggers; PRAGMA recursive_triggers = boolean; 會影響所有的語句執行。3.6.18以前,這個開關是不支援的。預設值是off.

  11. PRAGMA reverse_unordered_selects; PRAGMA reverse_unordered_selects = boolean; 當開啟此開關時,不帶order by的select語句,會輸出相反順序的結果。

  12. PRAGMA schema_version; PRAGMA schema_version = integer ; PRAGMA user_version; PRAGMA user_version = integer ; schema和user version是在資料庫檔案頭40,60位元組處的32位整數(大端表示)。 schema版本由sqlite內部維護,當schema改變時,就會增加該值。顯式改變該值非常危險。 user版本可以被應用程式使用。

  13. PRAGMA secure_delete; PRAGMA database.secure_delete; PRAGMA secure_delete = boolean PRAGMA database.secure_delete = boolean 設為ON時,刪除的內容會用0來覆蓋。預設值由巨集SQLITE_SECURE_DELETE 決定。那就是OFF了。

  14. PRAGMA short_column_names; PRAGMA short_column_names = boolean; deprecated.

  15. PRAGMA synchronous; PRAGMA synchronous = 0 | OFF | 1 | NORMAL | 2 | FULL; 查詢設定sync標誌值。預設值是FULL.

  16. PRAGMA table_info(table-name); 返回表的基本資訊

  17. 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 memory

  18. PRAGMA temp_store_directory; PRAGMA temp_store_directory = 'directory-name'; 設定或改變temp_store的目錄位置. deprecated.

  19. PRAGMA vdbe_listing = boolean; 用於DEBUG

  20. PRAGMA vdbe_trace = boolean; 用於DEBUG

  21. PRAGMA wal_autocheckpoint; PRAGMA wal_autocheckpoint=N; 設定WAL自動檢查點的間隔(以頁為單位), 預設值是1000。

  22. PRAGMA database.wal_checkpoint; PRAGMA database.wal_checkpoint(PASSIVE); PRAGMA database.wal_checkpoint(FULL); PRAGMA database.wal_checkpoint(RESTART);

  23. PRAGMA writable_schema = boolean; 當設為ON時,SQLITE_MASTER表可以執行CUD操作。這樣做很危險!!

相關文章