SQLite中字段順序和PAGE_SIZE對性能的影響


    1.背景

SQLite數據庫中有1張表,該表含若干個字段,其中有1個字段為BLOB類型,且BLOB字段不是最后1個字段。表結構類似如下(col3為BLOB字段):

    T (col1 INTEGER,col2 TEXT,col3 BLOB,col4 REAL,col5 TEXT)

    業務系統要遍歷這張表的內容,但查詢內容不包括BLOB字段,即查詢SQL類似如下:

    Select col1,col2,col4,col5 from T;

    2.問題說明

上述的使用模式,在表T較小的情況下運轉尚且良好,但當表T較大時(在我們的系統中.DB文件達到了100G,且BLOB占了主要的存儲),遍歷一次表需要很長的時間,長達幾個小時。那么對於這樣的使用場景,應該要如何去優化呢?

    3.優化思路

    若不考慮物理IO優化和操作系統優化,僅考慮DB優化,一般來說,優化無外乎如下幾種常用的方式:

  1. 索引。但對於需要進行遍歷訪問的表,通過索引顯然毫無優化空間,甚至會效率更低。因此索引的優化思路首先被放棄。
  2. SQL優化。但這個SQL,屬於最基本的查詢SQL,因此也沒有優化空間。
  3. 並行查詢。將之前1個進程訪問所有的記錄,改為多個進程分別訪問不同的記錄區間。這種方式可以嘗試。
  4. 參數優化。例如通過設置PAGE_SIZE參數,調整最小存儲單元PAGE的大小。
  5. 其它優化。基於DB文件格式和數據庫運行原理進行優化。

根據以上描述,下文我將從並行查詢、參數優化、其它優化3個方面進行優化實驗。

    4.優化實驗

4.1.並行查詢

    我將表T的所有記錄,按ROWID每5000條作為一個單元,然后開啟多個進程分別查詢不同的單元。通過這種方式的確實現了並行,但單個進程的IO吞吐會隨進程數的增加而減小,使總體的性能未有提升,下表是開啟不同個數的並行進程時,各進程獲得的IO吞吐量:

並行進程個數

各進程的IO吞吐

全部執行完畢耗時

1

18M/s

約2小時

4

4.4M/s

約2小時

6

2.9M/s

約2小時

8

2.2M/s

約2小時

    盡管可以實現多個進程同時工作,但對於SQLite來說,並行並沒有擴展IO的吞吐能力。因此並行查詢,不能起到優化效果。

4.2.參數優化

    SQLite可通過PRAGMA宏來設置不同的運行參數。通過分析所有可被設置的參數,我認為PAGE_SIZE參數可能會較明顯地影響優化效果。基於如下分析:

PAGE是SQLite的最小存儲單元,它是表擴張和收縮的基本單位,表中的記錄都存儲在PAGE中(類似於ORACLE中的block)。PAGE_SIZE用來指定PAGE的大小,不同版本有不同的默認值(v3.12之前是1024 Byte,v3.12之后是4096 Byte),改變默認值只能在創建.DB中第1張表之前進行(或改變默認值之后立刻執行VACUUM)。

我們的業務系統使用的數據庫版本小於v3.12,因此其默認的PAGE_SIZE為1KB,而通過分析數據,發現幾乎表中所有的記錄,其大小均大於1KB,甚至達到幾百KB。在PAGE中的存儲表記錄時(在SQLite中也稱為payload),會首先使用當前PAGE中剩余的存儲空間,當剩余空間不夠用時,會產生一個overflow page(溢出頁),然后繼續在溢出頁中存儲payload剩余的內容,空間仍然不夠用時,會繼續產生溢出頁,以此種方式直到將payload表達完整。其示意圖如下:

    假設一條記錄為3K,當PAGE_SIZE為1K時,完全查詢這一條記錄需要3次尋址(查找對應的PAGE);而當PAGE_SIZE為4K,完全查詢這一條記錄僅需1次尋址。

    制作了一個測試表,平均記錄大小為76KB,一共有12000條記錄,表大小約960M,設置不了不同的PAGE_SIZE,其查詢效率的對比如下:

PAGE_SIZE

該使用場景查詢耗時

1K

4.93s

2K

2.74s

4K

1.67s

8K

1.08s

16K

0.79

    根據上表可知,當表記錄較大時(超過PAGE_SIZE的大小),隨着PAGE_SIZE的增大,本使用場景的查詢耗時越小。查詢效率提升的倍數大致與PAGE_SIZE的倍數一致。

4.3.其它優化

    通過分析SQLite的文件格式可知,表記錄的所有字段的內容是連續排列的,這與ORACLE等數據庫是不同的(ORACLE對於LOB對象,僅在字段內容中記錄LOB的地址,而非實際LOB內容)。差別如下圖:

    對於SQLite,如果要查詢col4和col5,需要將col3 value完全"走過",當col3 value由於過大而分散存儲在多個溢出頁時,還需要"走過"所有這些溢出頁,雖然這些"走過"完全是無意義的,但仍然會發生IO。

    由於SQLite的文件格式有上述特征,因此只需將BLOB字段順序由第3位調整為最末位,即可避免對BLOB字段無效的IO"走過"。

    仍然使用4.2中的數據,設PAGE_SIZE為1K,將BLOB字段分別設為中間位置和最末位創建數據庫,比較性能如下:

PAGE_SIZE

BLOB字段的位置

該使用場景查詢耗時

1K

中間位置

4.93s

1K

最末位

0.28s

    將BLOB字段由中間位置調整為最未位之后,優化效果明顯,查詢效率約為調整前的17.6倍。

5.結論

    關於第一章背景中提到的優化場景,有如下兩種優化手段:

  1. 將BLOB字段由中間位置調整為最末位。此種優化手段優化效果非常明顯。
  2. 根據表記錄的大小,設置合適的PAGE_SIZE,以盡量減少溢出頁,進而減少IO次數。此種優化方式優化效果尚可,但沒有第一種優化手段效果明顯。


免責聲明!

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



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