SQLite 內建支援 SQL 查詢剖析,但預設未啟用。若要啟用查詢剖析支援,必須使用 下列選項 編譯 SQLite
-DSQLITE_ENABLE_STMT_SCANSTATUS
使用此選項編譯 SQLite 可啟用 sqlite3_stmt_scanstatus_v2() API,提供存取各種剖析指標的功能。本頁面後續內容將討論 SQLite 命令列殼層 使用這些指標產生的剖析報告,而非直接討論 API。
殼層產生的剖析報告與 EXPLAIN QUERY PLAN 指令產生的查詢計畫報告非常類似。本頁面假設讀者熟悉此格式。
在使用上述選項編譯的命令列殼層中,可使用「.scanstats on」指令啟用查詢剖析
sqlite> .scanstats on
啟用後,殼層會在執行每一個 SQL 查詢後自動輸出查詢剖析。可使用「.scanstats off」停用查詢剖析。例如
sqlite> .scanstats on sqlite> SELECT a FROM t1, t2 WHERE a IN (1,2,3) AND a=d+e; QUERY PLAN (cycles=255831538 [100%]) |--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) (cycles=60048488 [23%] loops=1 rows=3) `--SCAN t2 (cycles=133558052 [52%] loops=3 rows=150000)
考慮具有下列結構的資料庫
CREATE VIRTUAL TABLE ft USING fts5(text); CREATE TABLE t1(a, b); CREATE TABLE t2(c INTEGER PRIMARY KEY, d);
然後,在先執行「.scanstats on」後
sqlite3> SELECT * FROM t1, t2 WHERE t2.c=t1.a; <...query results...> QUERY PLAN (cycles=1140768 [100%]) |--SCAN t1 (cycles=455974 [40%] loops=1 rows=500) `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) (cycles=619820 [54%] loops=500 rows=250)
上述範例中,在「<...query results...>」片段之後的文字,是剛才執行的聯結查詢的剖析報告。剖析報告中類似 EXPLAIN QUERY PLAN 輸出的部分,指出查詢是透過對「t1」表格執行全表掃描,並針對每個拜訪的列,對「t2」表格以整數主鍵進行查詢。
「SCAN t1」列上的「loops=1」標記表示這個迴圈 (「t1」表格的全表掃描) 執行了一次。「rows=500」表示單次掃描拜訪了 500 列。
「SEARCH t2 USING ...」列包含註解「loops=500」,表示這個「迴圈」(實際上是透過整數主鍵進行查詢) 執行 500 次。這是有道理的,因為它針對「t1」全表掃描拜訪的每一列執行一次。「rows=250」表示這些 500 個迴圈總共拜訪了 250 列。換句話說,在「t2」表格上的整數主鍵查詢中,只有半數成功,另一半的查詢找不到列。
SEARCH 或 SCAN 條目的迴圈計數不一定與外部迴圈輸出的列數相同。例如,如果將上述查詢修改如下
sqlite3> SELECT * FROM t1, t2 WHERE t1.b<=100 AND t2.c=t1.a; <...query results...> QUERY PLAN (cycles=561002 [100%]) |--SCAN t1 (cycles=345950 [62%] loops=1 rows=500) `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) (cycles=128690 [23%] loops=100 rows=50)
這次,即使「SCAN t1」迴圈仍然拜訪 500 列,「SEARCH t2」查詢只執行 100 次。這是因為 SQLite 能夠捨棄不符合「t1.b<=100」約束的「t1」列。
「週期」測量是基於 CPU 時間戳記計數器,因此是牆上時鐘時間的良好代理。對於上述查詢,週期總數為 561002。對於兩個迴圈(「SCAN t1...」和「SEARCH t2...」)中的每一個,週期計數代表可直接歸因於該迴圈的運算所花費的時間。具體來說,這是花在導覽和從該迴圈的資料庫 b 樹中萃取資料的時間。這些值從未完全加總為查詢的總週期,因為 SQLite 執行其他內部運算,而這些運算無法直接歸因於任一迴圈。
「SCAN t1」迴圈的週期計數為 345950 - 查詢總數的 62%。由「SEARCH t1」迴圈執行的 100 次查詢耗費 128690 個週期,佔總數的 23%。
當使用虛擬表格時,「列」和「迴圈」指標與常規 SQLite 表格的迴圈具有相同的意義。「週期」測量是與迴圈相關的虛擬表格方法中消耗的總週期。例如
sqlite3> SELECT * FROM ft('sqlite'), t2 WHERE t2.c=ft.rowid; <...query results...> QUERY PLAN (cycles=836434 [100%] |--SCAN ft VIRTUAL TABLE INDEX 0:M1 (cycles=739602 [88%] loops=1 rows=48) `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) (cycles=62866 [8%] loops=48 rows=25)
在此情況下,fts5 表格「ft」上的單一查詢(迴圈 = 1)傳回 48 列(列 = 48)並消耗 739602 個週期(週期 = 739602),這大約是總查詢時間的 88%。
使用與前一節相同架構,考慮這個更複雜的範例
sqlite3> WITH cnt(i) AS ( SELECT 1 UNION SELECT i+1 FROM cnt WHERE i<100 ) SELECT *, (SELECT d FROM t2 WHERE c=ft.rowid) FROM (SELECT count(*), a FROM t1 GROUP BY a) AS v1 CROSS JOIN ft('sqlite'), cnt WHERE cnt.i=ft.rowid AND v1.a=ft.rowid; <...query results...> QUERY PLAN (cycles=177665334 [100%]) |--CO-ROUTINE v1 (cycles=4500444 [3%]) | |--SCAN t1 (cycles=397052 [0%] loops=1 rows=500) | `--USE TEMP B-TREE FOR GROUP BY |--MATERIALIZE cnt (cycles=1275068 [1%]) | |--SETUP | | `--SCAN CONSTANT ROW | `--RECURSIVE STEP | `--SCAN cnt (cycles=129166 [0%] loops=100 rows=100) |--SCAN v1 (loops=1 rows=500) |--SCAN ft VIRTUAL TABLE INDEX 0:M1= (cycles=161874340 [91%] loops=500 rows=271) |--SCAN cnt (cycles=7336350 [4%] loops=95 rows=9500) `--CORRELATED SCALAR SUBQUERY 3 (cycles=168538 [0%] loops=37) `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?) (cycles=94724 [0%] loops=37 rows=21)
上述範例最複雜的部分是了解查詢計畫 - 報告中也會由 EXPLAIN QUERY PLAN 指令產生的一部分。其他重點如下
子查詢「v1」實作為 協同常式。在此情況下,子查詢會個別報告,而且整個子查詢都有「週期」計數。還有一行「SCAN v1」 - 這代表從主查詢呼叫子查詢協同常式。此項目沒有相關的週期,因為子查詢的全部成本都歸因於協同常式。它確實有「迴圈」和「列」值 - 子查詢掃描一次並傳回 500 列。
遞迴子查詢「cnt」在執行主要查詢之前已具象化(快取在暫存表中)。具象化的全部成本都歸因於「具象化 cnt」元素。還有一個「掃描 cnt」項目,代表已具象化子查詢的掃描。與此項目相關聯的週期值代表掃描包含已具象化子查詢的暫存表所花費的時間,這與用於填入該表的時間不同。
標量子查詢也有週期和迴圈測量。這些分別代表執行子查詢時消耗的總週期和執行次數。
當一個項目是另一個項目的父項時,例如「相關標量子查詢 3」和「使用 t2 搜尋...」,則與父項相關聯的週期值包括與所有子元素相關聯的週期。在所有情況下,百分比值都與查詢使用的總週期相關,而不是父項使用的週期。
下列查詢使用自動索引和外部排序
sqlite> SELECT * FROM t2, (SELECT count(*) AS cnt, d FROM t2 GROUP BY d) AS v2 WHERE v2.d=t2.d AND t2.d>100 ORDER BY v2.cnt; <...query results...> QUERY PLAN (cycles=6234376 [100%]) |--MATERIALIZE v2 (cycles=2351916 [38%]) | |--SCAN t2 (cycles=188428 [3%] loops=1 rows=250) | `--USE TEMP B-TREE FOR GROUP BY |--SCAN t2 (cycles=455736 [7%] loops=1 rows=250) |--CREATE AUTOMATIC INDEX ON v2(d, cnt) (cycles=1668380 [27%] loops=1 rows=250) |--SEARCH v2 USING AUTOMATIC COVERING INDEX (d=?) (cycles=932824 [15%] loops=200 rows=200) `--USE TEMP B-TREE FOR ORDER BY (cycles=662456 [11%] loops=1 rows=200)
重點如下
此查詢將子查詢具象化為暫存表,然後在其中建立自動(即暫時)索引,再使用該索引最佳化聯結。這三個步驟(「具象化 v2」、「建立自動索引」和「使用自動索引搜尋...」)都有各自的週期計數。「建立自動索引」列相關聯的「列」代表索引中包含的總列數。「使用自動索引搜尋...」列相關聯的「迴圈」和「列」代表使用索引的查詢次數和這些查詢找到的總列數。
外部排序「使用暫存 B 樹進行排序」也另外計算。週期計數代表排序傳回列時消耗的額外週期,超過以任意順序傳回列時會使用的週期。列計數代表排序的列數。
除了「.scanstats on」用於啟用剖析和「.scanstats off」用於停用剖析外,shell 也接受「.scanstats est」
sqlite> .scanstats est
這會啟用一種特殊的剖析報告,其中包含與查詢剖析的每個「SCAN...」和「SEARCH...」元素相關聯的兩個額外值
sqlite> SELECT a FROM t1, t2 WHERE a IN (1,2,3) AND a=d+e ORDER BY a; <query results...> QUERY PLAN (cycles=264725190 [100%] |--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?) (cycles=60511568 [23%] loops=1 rows=3 rpl=3.0 est=3.0) `--SCAN t2 (cycles=139461608 [53%] loops=3 rows=150000 rpl=50000.0 est=1048576.0)
也支援「.scanstats vm」指令。這會啟用低階剖析報告,顯示每個 VM 指令執行的次數,以及執行時經過的時脈週期百分比
sqlite> .scanstats vm
然後
sqlite> SELECT count(*) FROM t2 WHERE (d % 5) = 0; <query results...> addr cycles nexec opcode p1 p2 p3 p4 p5 comment ---- ------ ------ ------------- ---- ---- ---- ------------- -- ------------- 0 0.0% 1 Init 1 18 0 0 Start at 18 1 0.0% 1 Null 0 1 1 0 r[1..1]=NULL 2 0.0% 1 OpenRead 0 2 0 2 0 root=2 iDb=0; t2 3 0.0% 1 ColumnsUsed 0 0 0 2 0 4 0.0% 1 Explain 4 0 0 SCAN t2 0 5 0.0% 1 CursorHint 0 0 0 EQ(REM(c1,5),0) 0 6 0.0% 1 Rewind 0 14 0 0 7 46.86% 150000 Column 0 1 3 0 r[3]= cursor 0 column 1 8 18.94% 150000 Remainder 4 3 2 0 r[2]=r[3]%r[4] 9 5.41% 150000 ReleaseReg 3 1 0 0 release r[3] mask 0 10 12.09% 150000 Ne 5 13 2 80 if r[2]!=r[5] goto 13 11 1.02% 30000 ReleaseReg 2 1 0 0 release r[2] mask 0 12 2.95% 30000 AggStep1 0 0 1 count(0) 0 accum=r[1] step(r[0]) 13 12.72% 150000 Next 0 7 0 1 14 0.0% 1 AggFinal 1 0 0 count(0) 0 accum=r[1] N=0 15 0.0% 1 Copy 1 6 0 0 r[6]=r[1] 16 0.0% 1 ResultRow 6 1 0 0 output=r[6] 17 0.01% 1 Halt 0 0 0 0 18 0.0% 1 Transaction 0 0 1 0 1 usesStmtJournal=0 19 0.0% 1 Integer 5 4 0 0 r[4]=5 20 0.0% 1 Integer 0 5 0 0 r[5]=0 21 0.0% 1 Goto 0 1 0 0
此頁面最後修改於 2023-07-27 20:27:55 UTC