SQLite——臨時文件 & 內存數據庫


SQLite 如何變成 內存數據庫

SQLite數據庫通常存儲在單個普通磁盤文件中。但是,在某些情況下,數據庫可能存儲在內存中

強制SQLite數據庫單純的存在於內存中的最常用方法是使用特殊文件名“:memory:” 打開數據庫。換句話說,不是將真實磁盤文件的名稱傳遞給sqlite3_open(),sqlite3_open16()或 sqlite3_open_v2()函數之一,而是傳入字符串“:memory:”。例如:

rc = sqlite3_open(“:memory:”,&db);

調用此接口完成后,不會打開任何磁盤文件。而是在內存中創建一個新的數據庫。數據庫連接關閉后,數據庫就不再存在。每一個memory數據庫彼此不同。因此,打開兩個數據庫連接,每個數據庫連接的文件名為“:memory:”,將創建兩個獨立的內存數據庫。

特殊文件名“:memory:”可用於允許數據庫文件名的任何位置。例如,它可以被用作文件名中的ATTACH命令:

ATTACH DATABASE ':memory:' AS aux1;

請注意,為了應用特殊的“:memory:”名稱並創建純內存數據庫,文件名中不能有其他文本。因此,可以通過添加路徑名在文件中創建基於磁盤的數據庫,如下所示:"./:memory:"。

使用URI文件名時,特殊的“:memory:”文件名也可以使用。例如:

rc = sqlite3_open(“file :: memory:”,&db);

要么,

ATTACH DATABASE 'file::memory:' AS aux1;

內存數據庫和共享緩存

如果使用URI文件名打開內存數據庫,則允許它們使用共享緩存如果使用未加修飾的“:memory:”名稱來指定內存數據庫,那么該數據庫始終具有專用高速緩存,並且僅對最初打開它的數據庫連接可見。但是,可以通過兩個或多個數據庫連接打開相同的內存數據庫,如下所示:

rc = sqlite3_open("file::memory:?cache=shared", &db);

要么,

ATTACH DATABASE 'file::memory:?cache=shared' AS aux1;

允許單獨的數據庫連接共享相同的內存數據庫。當然,共享內存數據庫的所有數據庫連接都需要在同一個進程中當數據庫的最后一個連接關閉時,將自動刪除數據庫並回收內存。

如果在單個進程中需要兩個或多個不同同時可共享的內存數據庫,則mode = memory查詢參數可與URI文件名一起使用以創建命名的內存數據庫:

rc = sqlite3_open("file:memdb1?mode=memory&cache=shared", &db);

要么,

ATTACH DATABASE 'file:memdb1?mode=memory&cache=shared' AS aux1;

以這種方式命名內存數據庫時,它將僅與使用完全相同名稱的另一個連接共享其緩存

臨時數據庫

當傳遞給sqlite3_open()或 ATTACH的數據庫文件的名稱是空字符串時,則會創建一個新的臨時文件來保存數據庫

rc = sqlite3_open("", &db);

ATTACH DATABASE '' AS aux2;

每次都會創建一個不同的臨時文件,因此就像使用特殊的“:memory:”字符串一樣,兩個到臨時數據庫的數據庫連接都有自己的私有數據庫。創建它們的連接關閉時,將自動刪除臨時數據庫

即使為每個臨時數據庫分配了磁盤文件,實際上臨時數據庫通常駐留在內存中的pager緩存中,因此“:memory:”創建的純內存數據庫與臨時數據庫之間的差別很小。由空文件名創建。唯一的區別是“:memory:”數據庫必須始終保留在內存中,而如果數據庫變大或SQLite受到內存壓力,臨時數據庫的某些部分可能會刷新到磁盤

前面的段落描述了默認SQLite配置下臨時數據庫的行為。如果需要,應用程序可以使用 temp_store編譯指示和SQLITE_TEMP_STORE編譯時參數來強制臨時數據庫表現為純內存數據庫

 

SQLite 文件使用 Memory 模式

SQLiteConnection source = new SQLiteConnection("Data Source=c:\\test.db");
source.Open();
 
using (SQLiteConnection destination = new SQLiteConnection("Data Source=:memory:"))
{
  destination.Open();               
 
  // copy db file to memory
  source.BackupDatabase(destination, "main", "main",-1, null, 0);
  source.Close();
 
  // insert, select ,...        
  using (SQLiteCommand command = new SQLiteCommand())
  {
    command.CommandText = "INSERT INTO t1 (x) VALUES('some new value');";
 
    command.Connection = destination;
    command.ExecuteNonQuery();
  }             
 
  source = new SQLiteConnection("Data Source=c:\\test.db");
  source.Open();
 
  // save memory db to file
  destination.BackupDatabase(source, "main", "main",-1, null, 0);
  source.Close();               
}

  

SQLite剖析(6):臨時文件和內存數據庫

1、7種臨時文件
    內容整理自http://sqlite.org/tempfiles.html。
    SQLite的一個不同特性是一個數據庫由單個磁盤文件構成。這簡化了SQLite的使用,因為移動或備份數據庫只要拷貝單個文件即可。這也使得SQLite適合用作應用程序文件格式。但是,當在單個文件中存儲一個數據庫時,SQLite會在處理數據庫的過程中使用許多臨時文件。
    SQLite目前使用7種不同類型的臨時文件:
    * 回滾日志(Rollback journals)
    * 主日志(Master journals)
    * SQL語句日志(Statement journals)
    * 臨時數據庫(TEMP databases)
    * 視圖和子查詢的持久化(Materializations of views and subqueries)
    * 臨時索引(Transient indices)
    * VACUUM使用的臨時數據庫(Transient databases used by VACUUM)
    (1)回滾日志
    回滾日志是一個臨時文件,用來實現原子提交和回滾功能。回滾日志總是位於與數據庫文件相同的目錄下,文件名為數據庫文件名后加"-journal"。回滾日志通常在一個事務首次開始時創建,在一個事務提交或回滾時刪除。如果沒有回滾日志,SQLite將不能回滾一個未完成的事務,並且在事務執行的中間某時刻若發生系統崩潰或斷電,數據庫也會被損壞。回滾日志通常在事務的起點和終點創建和銷毀,但也會有一些例外規則
    如果崩潰或斷電發生在事務的中間某時刻,則在硬盤上會留有回滾日志。在下次另外一個應用程序嘗試打開數據庫文件時,它會通知存在回滾日志(我們稱之為“熱日志”),並使用日志中的信息來把數據庫恢復到未完成事務開始之前的狀態。這就是SQLite實現原子提交的基本原理
    如果應用程序使用指令"PRAGMA locking_mode=EXCLUSIVE;"把SQLite置於排斥鎖模式下,則SQLite在帶排斥鎖模式會話的事務開始時創建一個新的回滾日志,在事務結束不會刪除回滾日志回滾日志可能會被縮小,或者它的頭部可能會被清零(取決於你使用的SQLite版本),但文件不會被刪除,直到排斥訪問模式退出時回滾日志才會被刪除
    回滾日志的創建和刪除也可以用日志模式PRAGMA指令來更改

默認的日志模式是DELETE,即在每個事務結束時刪除回滾日志。

PERSIST日志模式則放棄刪除日志文件,而是把日志文件的頭部清零,以防止其他進程回滾日志,因此這與刪除日志文件有同樣的效果,雖然實際上並沒有從磁盤上刪除日志文件。也就是說,日志模式PERSIST展示的行為與EXCLUSIVE鎖模式相同

OFF日志模式讓SQLite放棄在開始時創建回滾日志,它會禁用SQLite的原子提交和回滾功能,讓ROLLBACK命令不可用。如果使用OFF日志模式的事務在中間某時刻發生崩潰或斷電,則數據庫文件不能恢復,可能會被損壞
    (2)主日志文件
    主日志文件用於多數據庫操作的原子提交過程中,即一個事務修改多個數據庫,這些數據庫通過ATTACH命令被關聯在一個數據庫連接上。主日志文件總是位於與主數據庫文件相同的目錄下(主數據庫文件是在調用sqlite3_open(), sqlite3_open16()或sqlite3_open_v2()創建數據庫連接時使用的數據庫),跟一個隨機的后綴。主日志文件中包含所有關聯的輔助數據庫名稱。多數據庫事務提交時主日志文件就會被刪除
    主日志文件只會在這樣的情況下創建:一個數據連接與通過ATTACH關聯的兩個或多個數據庫進行會話,並且一個事務修改多個數據庫文件。如果沒有主日志文件,多數據庫事務對每個單獨數據庫的提交是原子性的,但對整個多數據庫一起則不是原子性的。也就是說,如果提交在中間某時刻因為崩潰或斷電而中斷,則可能對一個數據庫的更改完成,而對另一個數據庫的更改被回滾。主日志文件確保所有數據庫的所有更改要么一起回滾,要么一起提交
    (3)SQL語句日志文件
    SQL語句日志文件用於回滾大型事務中一個單獨SQL語句的部分結果。例如,假設一條UPDATE語句嘗試修改數據庫中的100行,但在修改完50行后,因為意外情況而終止。SQL語句日志用來撤消這50行的更改,以便數據庫恢復到語句執行前的狀態
    SQL語句日志只會在一條UPDATE或INSERT語句修改數據庫的多行,且意外終止或在觸發器中拋出異常因而需要撤消部分結果的情況下創建如果UPDATE或INSERT沒有包含在BEGIN...COMMIT中,且在同一數據庫連接上沒有其他活動的SQL語句,則無需創建語句日志,因為可以使用原來的回滾日志。如果使用了可靠的沖突解決算法,則語句日志也會被忽略,例如:
UPDATE OR FAIL ...
UPDATE OR IGNORE ...
UPDATE OR REPLACE ...
INSERT OR FAIL ...
INSERT OR IGNORE ...
INSERT OR REPLACE ...
REPLACE INTO ....
    SQL語句日志文件使用隨機的文件名,不一定要在與主數據庫相同的目錄下,在事務結束時自動刪除。SQL語句日志的空間大小只是UPDATE或INSERT語句完成的更改部分的比例大小
    (4)臨時數據庫
    使用"CREATE TEMP TABLE"命令創建的表格只在執行這條命令的數據庫連接上可見。這些TEMP表格,以及任何關聯的索引、觸發器和視圖,一起存放在一個單獨的臨時數據庫文件中,這個臨時數據庫在首次遇到"CREATE TEMP TABLE"命令時創建。這個單獨的臨時數據庫文件也有一個關聯的回滾日志。用來存儲TEMP表格的臨時數據庫會在使用sqlite3_close()關閉數據庫連接時自動刪除。
    臨時數據數據庫文件與通過ATTACH命令添加的輔助數據庫文件非常類似,不過帶有一些特殊屬性臨時數據庫文件總是在數據庫連接關閉時自動刪除。臨時數據庫總是使用synchronous=OFF和journal_mode=PERSIST這兩條PRAGMA指令設置。並且,臨時數據庫不能使用DETACH,別的進程也不能通過ATTACH關聯臨時數據庫。臨時數據庫文件和它的回滾日志只有在應用程序使用"CREATE TEMP TABLE"命令時才會被創建。
    (5)視圖和子查詢的持久
    包含子查詢的查詢命令必須在某個時刻單獨執行子查詢並把結果存儲在一個臨時表格中,然后使用臨時表格中的內容來執行外部查詢。我們稱之為“持久化”子查詢SQLite的查詢優化器會嘗試避免持久化,但有時候這是不可避免的持久化過程創建的每個臨時表格存儲在它們自己單獨的臨時文件中,在查詢結束時自動刪除。這些臨時表格的大小取決於子查詢實體的數據數量
    位於IN操作符右邊的子查詢通常必須被持久化。例如:
    SELECT * FROM ex1 WHERE ex1.a IN (SELECT b FROM ex2);
    在上面的查詢命令中,子查詢"SELECT b FROM ex2"的執行結果被存儲在一個臨時表格中(實際為一個臨時索引),它通過二進制搜索的方式來確定是否存在一個值ex2.b。一旦這個臨時表格被創建,就運行外部查詢,對每個預期的結果行檢查ex1.a是否包含在臨時表中,如果為true,則輸出這個結果行
    為了避免創建臨時表格,查詢可以重寫為以下形式:
    SELECT * FROM ex1 WHERE EXISTS(SELECT 1 FROM ex2 WHERE ex2.b=ex1.a);
    如果在列ex2.b上有索引,則3.5.4及以后版本的SQLite會自動做這樣的重寫
    如果IN操作符的右邊部分是值列表,像下面這樣:
    SELECT * FROM ex1 WHERE a IN (1,2,3);
    位於IN右邊的值列表被認為是一個子查詢,必須要持久化。也就是說,這個查詢行為相當於下面這樣:
    SELECT * FROM ex1 WHERE a IN (SELECT 1 UNION ALL
                                 SELECT 2 UNION ALL
                                 SELECT 3);
    當IN右邊是一個值列表時,會用一個臨時索引來持有這些值
    當子查詢出現在SELECT命令的FROM子句中時,也會進行持久化。例如:
    SELECT * FROM ex1 JOIN (SELECT b FROM ex2) AS t ON t.b=ex1.a;
    根據查詢,SQLite可能需要持久化"(SELECT b FROM ex2)"子查詢到一個臨時表格中,然后在ex1和臨時表格之間執行連接。查詢優化器會嘗試“扁平化(flattening)”這個查詢來避免子查詢的持久化。在這個例子中,查詢可以被扁平化,SQLite將自動把這個查詢轉換成
    SELECT ex1.*, ex2.b FROM ex1 JOIN ex2 ON ex2.b=ex1.a;
    更復雜的查詢可能會,也可能不會進行扁平化處理以避免臨時表格是否扁平化處理取決於子查詢或外部查詢是否包含聚合函數、ORDER BY或GROUP BY子句、LIMIT子句,等等
    (6)臨時索引
    SQLite使用臨時索引來實現很多SQL語言特性,包括:
    * ORDER BY或GROUP BY子句
    * 聚合查詢中的DISTINCT關鍵字
    * 復合式SELECT語句,即有UNION, EXCEPT或INTERSECT等連接子句
    每個臨時索引存放在它自己的臨時文件中,在SQL語句執行結束時被自動刪除
    SQLite會嘗試使用已存在的索引來實現ORDER BY子句。如果在指定的字段上已存在索引,SQLite將遍歷該索引(而不是創建臨時索引)來提取需要的信息,並且以指定的排序輸出結果行。如果SQLite沒有找到合適的索引,則執行查詢並把每行存儲在一個臨時索引中,索引的關鍵字為ORDER BY指定的字段。然后SQLite返回並從頭到尾遍歷臨時索引,以指定的排序輸出每行。
    對於GROUP BY子句,SQLite根據指定字段對輸出行進行排序。每個輸出行與先前行進行比較,看它是否屬於新的組。GROUP BY字段的排序與ORDER BY字段的排序是相同的。如果有存在的索引就使用它,如果沒有已存在的索引,則創建臨時索引。
    聚合查詢上的DISTINCT關鍵字會在一個臨時文件中創建臨時索引,並把每行結果存儲到索引中。對新的結果行,如果在臨時索引中已存在,則忽略它。
    復合查詢的UNION運算符會在一個臨時文件創建臨時索引,並把左邊和右邊子查詢結果存儲到索引中,忽略重復的行。當兩個子查詢執行完后,從頭到尾遍歷臨時索引來產生最后的輸出。
    復合查詢的EXCEPT運算符會在一個臨時文件創建臨時索引,並把左邊子查詢結果存儲到臨時索引中,然后從索引中移除右邊子查詢的結果,最后從頭到尾遍歷臨時索引以得到最后的輸出。
    復合查詢的EXCEPT運算符會創建兩個獨立的臨時索引,它們位於兩個獨立的臨時文件中。左邊和右邊子查詢被執行並存放到各自的臨時索引中。然后一起遍歷兩個索引,輸出同時存在於兩個索引中的結果。
    注意復合查詢的UNION ALL運算符自己並不使用臨時索引,當然UNION ALL左邊和右邊的子查詢可能會單獨使用臨時索引,這取決於它們是怎么復合的。
    (7)VACUUM命令使用的臨時數據庫
    VACUUM命令會先創建一個臨時文件,然后重建整個數據庫並寫入到該臨時文件中。之后將臨時文件中的內容拷貝回原有的數據庫文件中,最后刪除該臨時文件。VACUUM命令創建的臨時文件不會比原有數據庫文件大
    2、SQLITE_TEMP_STORE編譯時參數和PRAGMA指令
    回滾日志、主日志和SQL語句日志文件總是會被寫入磁盤,但其它類型的臨時文件可能存放在內存中而不會寫入磁盤(這樣可以減少大量的IO操作),是寫入磁盤還是存放於內存中取決於SQLITE_TEMP_STORE編譯時參數,temp_store pragma運行時指令,以及臨時文件的大小。
    SQLITE_TEMP_STORE編譯時參數是源代碼中的宏定義(#define),其取值范圍是0到3(缺省值為1),如下:
    * 等於0時,臨時文件總是存儲在磁盤上,而不會考慮temp_store pragma指令的設置。
    * 等於1時,臨時文件缺省存儲在磁盤上,但是該值可以被temp_store pragma指令覆蓋。
    * 等於2時,臨時文件缺省存儲在內存中,但是該值可以被temp_store pragma指令覆蓋。
    * 等於3時,臨時文件總是存儲在內存中,而不會考慮temp_store pragma指令的設置。
    temp_store pragma指令的取值范圍是0到2(缺省值為0)在程序運行時該指令可以被動態的設置,如下:
    * 等於0時,臨時文件的存儲行為完全由SQLITE_TEMP_STORE編譯期參數確定
    * 等於1時,如果編譯期參數SQLITE_TEMP_STORE指定使用內存存儲臨時文件,那么該指令將覆蓋這一行為,使用磁盤存儲。否則直接使用SQLITE_TEMP_STORE的行為。
    * 等於2時,如果編譯期參數SQLITE_TEMP_STORE指定使用磁盤存儲臨時文件,那么該指令將覆蓋這一行為,使用內存存儲。否則直接使用SQLITE_TEMP_STORE的行為。
    重申一下,SQLITE_TEMP_STORE編譯時參數temp_store pragma指令只影響除回滾日志和主日志之外的臨時文件。這兩種日志總是會被寫入到磁盤的。
    對於以上兩個參數,都有參數值表示缺省情況是存儲在內存中的,只有當臨時文件的大小超過一定的閾值后才會根據一定的算法,將部分數據寫入到磁盤中,以免臨時文件占用過多的內存而影響其它程序的執行效率
    3、其他臨時文件優化
    SQLite對當前讀寫的數據庫頁面采用了Page Cache的緩沖優化機制,因此即便臨時文件被指定存儲在磁盤上,也只有當該文件的大小增長到一定的尺寸后(導致頁面緩存填滿)才有可能被SQLite刷新到磁盤文件上,在此之前它們仍將駐留在內存中。這就意味着對於大多數場景,如果臨時表和臨時索引的數據量相對較少(頁面緩存足夠存放它們),那么它們是不會被寫到磁盤中的,當然也就不會有磁盤IO發生。只有當它們增長到內存不能容納的時候才會被刷新到磁盤文件中的
    每個臨時表格和索引都有自己的頁緩存,它們能存放最大多少個數據庫頁面由SQLITE_DEFAULT_TEMP_CACHE_SIZE編譯期參數來確定,這個參數指定了臨時表和索引在占用多少Page Cache時才需要被刷新到磁盤文件,該參數的缺省值為500頁。這個參數值不能在運行時修改。
    4、內存數據庫
    內容整理自http://sqlite.org/inmemorydb.html。
    在SQLite中,數據庫通常是存儲在磁盤文件中的。然而在有些情況下,我們可以讓數據庫始終駐留在內存中。最常用的一種方式是在調用sqlite3_open(), sqlite3_open16()或sqlite3_open_v2() 時,數據庫文件名參數指定為":memory:",如:
    rc = sqlite3_open(":memory:", &db);
    在調用完以上函數后,不會有任何磁盤文件被生成,取而代之的是,一個新的數據庫在純內存中被成功創建了由於沒有持久化,該數據庫在當前數據庫連接被關閉后就會立刻消失。需要注意的是,每個:memory:數據庫是不同的數據庫,也就是說,用文件名":memory:"打開兩個數據庫連接將創建兩個獨立的內在數據庫
    文件名":memory:"可以用在任何允許使用數據庫文件名的地方。例如,它可以用於ATTACH命令中,讓內存數據庫像其他普通數據庫一樣,附加到當前的連接中,如:
    ATTACH DATABASE ':memory:' AS aux1;
    注意在創建內存數據庫時,只能用文件名":memory:",不能包含其他文本,例如"./:memory:",那樣會創建一個基於磁盤文件的數據庫。在使用URI格式的文件名時,也可以使用":memory:",例如:
    rc = sqlite3_open("file::memory:", &db);
    或者 ATTACH DATABASE 'file::memory:' AS aux1;
    如果內存數據庫使用URI文件名打開,則它可以使用共享緩存。如果通過未修飾的":memory"名來指定內存數據庫,則這個數據庫總是有一個私有的對其他連接不可見的緩存。如果使用URI文件名,則同樣的內存數據庫可以被兩個或多個數據庫連接打開,例如:
    rc = sqlite3_open("file::memory:?cache=shared", &db);
    或者 ATTACH DATABASE 'file::memory:?cache=shared' AS aux1;
    這使得多個數據庫連接可以共享同一個內存數據庫。當然,共享一個內存數據庫的這些連接需要在同一個進程中當最后一個數據庫連接關閉時,內存數據庫自動被刪除
    如果需要在一個進程中使用多個不同的但可共享的內存數據庫,可以在URI文件名中附加mode=memory查詢參數來創建一個命名的內存數據庫:
    rc = sqlite3_open("file:memdb1?mode=memory&cache=shared", &db);
    或者  TTACH DATABASE 'file:memdb1?mode=memory&cache=shared' AS aux1;
    以這種方式命名的內存數據庫,只會與名字精確相同的另一個連接共享它的緩存。
    5、空文件名對應的臨時數據庫
    在調用sqlite3_open()函數或執行ATTACH命令時,如果數據庫文件參數傳的是空字符串,那么一個新的臨時文件將被創建以作為臨時數據庫的存儲文件,如:
    rc = sqlite3_open("", &db);
    或  ATTACH DATABASE '' AS aux2;
    每次都會創建不同的臨時文件,和內存數據庫非常相似,兩個連接創建的臨時數據庫也是各自獨立的,在連接關閉后臨時數據庫將自動消失,其存儲文件也將被自動刪除
    盡管磁盤文件被創建用於存儲臨時數據庫中的數據信息,但是實際上臨時數據庫也會和內存數據庫一樣通常駐留在內存中,唯一不同的是,當臨時數據庫中數據量過大時,SQLite為了保證有更多的內存可用於其它操作,因此會將臨時數據庫中的部分數據寫到磁盤文件中,而內存數據庫則始終會將數據存放在內存中。  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM