![]() |
|
|||
WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
SQLite содержит встроенную поддержку профилирования SQL-запросов, но это
не позволено по умолчанию. Чтобы позволить поддержку профилирования запроса,
SQLite должен быть собран с
опцией: Сборка SQLite с этим выбором позволяет
sqlite3_stmt_scanstatus_v2() API,
который обеспечивает доступ к различным профильным метрикам.
Остаток этой страницы обсуждает профильные отчеты, произведенные
оболочкой командной строки SQLite, используя эти
метрики, а не API непосредственно. Профильные отчеты, произведенные оболочкой,
очень похожи на отчеты о плане запросов, произведенные
EXPLAIN QUERY PLAN.
Эта страница предполагает, что читатель знаком с этим форматом. В оболочке командной строки, собранной с выбором выше, профилирование
запроса позволено, используя команду ".scanstats on": После того, как позволено, оболочка автоматически производит профиль
запроса после того, как каждый SQL-запрос выполнен.
Профилирование запроса может быть отключено, используя ".scanstats off": Consider a database with the following schema:
Затем после первого выполнения ".scanstats on": Текст в примере ниже "<...query results...>"
является отчетом о профиле для запроса соединения.
Части отчета о профиле, которые подобны
EXPLAIN QUERY PLAN, указывают, что запрос
осуществляется, делая полное сканирование таблицы "t1",
с поиском INTEGER PRIMARY KEY на таблице "t2" для каждой
строки, которую посещают. Формат "loops=1" в строке "SCAN t1" указывает, что этот цикл это
полное сканирование таблицы "t1" выполнен точно однажды. "rows=500"
указывает, что тот единственный просмотр посетил 500 строк. Строка "SEARCH t2 USING ..." содержит аннотацию "loops=500",
чтобы указать, что этот цикл (действительно поиск INTEGER PRIMARY KEY)
отработал 500 раз. Смысл: он сработал по разу для каждой строки, которую
посещает полное сканирование таблицы "t1". "rows=250" означает, что в целом
те 500 циклов посетили 250 строк. Другими словами, только половина поисков
INTEGER PRIMARY KEY на t2 были успешны, для другой половины поисков не было
никакой строки, чтобы найти. Счетчик циклов для SEARCH и SCAN
является не обязательно тем же самым, как количество строк, произведенных
внешним циклом. Например, если запрос выше был изменен следующим образом: На этот раз, даже при том, что "SCAN t1" все еще посещает 500 строк, поиск
"SEARCH t2" выполняется только 100 раз. Это вызвано тем, что SQLite смог
отказаться от строк в t1, которые не соответствовали "t1.b<=100". Измерения "cycles" основаны на
CPU time-stamp
counter, и являются хорошим полномочием в течение времени часов.
Для запроса выше общее количество циклов было 561002. Для каждого из этих
двух циклов ("SCAN t1..." и "SEARCH t2...")
счетчик циклов представляет время, проведенное в операциях, которые могут
быть непосредственно приписаны только этому циклу.
Определенно, это время, проведенное, проводя и извлекая данные из b-деревьев
базы данных для того цикла. Эти значения никогда не составляют в целом полные
циклы для запроса, поскольку есть другие внутренние операции, выполненные
SQLite, которые не относятся непосредственно ни к одному циклу. Счетчик циклов для "SCAN t1" был 345950, это 62%
общего количества для запроса. Эти 100 поисков, выполненных циклом
"SEARCH t1", заняли 128690 циклов, 23% от общего количества. Когда виртуальная таблица используется, у метрик "rows" и "loops"
есть то же самое значение, что касается циклов на обычных таблицах SQLite.
"cycles" являются полными циклами, потребляемыми в рамках методов
виртуальных таблиц, связанных с циклом. Например: В этом случае единый запрос (loops=1) на таблице fts5 "ft" вернул 48
строк (rows=48) и занял 739602 циклов (cycles=739602),
что составляло примерно 88% полного времени выполнения запроса. Используя ту же самую схему, как в предыдущей секции, рассмотрите этот
более сложный пример: Самая сложная часть примера выше понимает план запросов это
часть отчета, который был бы также произведен
EXPLAIN QUERY PLAN. Другие интересные места: Подзапрос "v1" осуществляется как
созапрос.
В этом случае относительно подзапроса сообщают отдельно количество циклов
для всего подзапроса. Есть также строка "SCAN v1", это представляет подвызов
созапроса от главного запроса. У этого входа нет циклов, связанных с ним,
поскольку все значение подзапроса приписано созапросу. У этого действительно
есть значения "loops" и "rows": подзапрос просматривается
однажды и возвращает 500 строк. Рекурсивный подзапрос "cnt" осуществлен (кэшированный в таблице temp)
прежде, чем главным запросом будут управлять. Все значение материализации
приписано "MATERIALIZE cnt". Есть также пункт "SCAN cnt",
представляющий просмотры осуществленного подзапроса.
Значение циклов, связанное с этим пунктом, представляет время, потраченное,
чтобы просмотреть временную таблицу, содержащую осуществленный подзапрос,
который является отдельным от циклов, используемых,
чтобы наполнить его. Есть циклы и измерения для скалярного подзапроса также.
Они представляют полные циклы, потребляемые, выполняя подзапрос и число раз,
которое это было выполнено, соответственно. Где один пункт это родитель другого, как в "CORRELATED SCALAR
SUBQUERY 3" и "SEARCH t2 USING...", значение циклов, связанное с родителем,
включает те циклы, связанные со всеми дочерними элементами. Во всех случаях
значения процента касаются полных циклов, используемых запросом, не
циклов, используемых родителем. Следующий запрос использует
автоматический индекс и внешнюю сортировку: Здесь интересно следующее: Этот запрос осуществляет подзапрос в таблице temp,
затем создает автоматический (то есть, переходный) индекс на нем, затем
использует тот индекс, чтобы оптимизировать соединение. Все три из этих
шагов, "MATERIALIZE v2", "CREATE AUTOMATIC INDEX" и "SEARCH ... USING
AUTOMATIC INDEX", имеют отдельное количество циклов.
"rows", связанное со строкой "CREATE AUTOMATIC INDEX",
представляет общее количество строк, включенных в индекс.
"loops" и "rows" связанные со строкой "SEARCH ... USING AUTOMATIC INDEX",
представляют количество поисков индекса, которое использовалось,
и общее количество строк, найденных теми поисками. Внешняя сортировка "USE TEMP B-TREE FOR ORDER BY"
также составляется отдельно. Количество циклов представляет дополнительные
циклы, потребляемые, сортируя возвращенные строки
выше тех, которые использовались бы, если строки
были возвращены в произвольном порядке. Количество строк представляет
количество сортированных строк. Кроме ".scanstats on", чтобы включить профилирование и ".scanstats off",
чтобы выключить, оболочка также принимает ".scanstats est": Это позволяет специальный вид профилирования отчета, который включает
два дополнительных значения, связанных с каждым элементом "SCAN..." и
"SEARCH..." профиля запроса: Также поддержана команда ".scanstats vm".
Это позволяет профильный отчет низшего уровня, показывая сколько раз каждая
инструкция VM была выполнена,
и процент тактов, которые прошли, в то время как это выполнялось: Затем:
Choose any three.
1. Обзор
-DSQLITE_ENABLE_STMT_SCANSTATUS
sqlite> .scanstats on
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)
2. Простые случаи: строки и циклы
CREATE VIRTUAL TABLE ft USING fts5(text);
CREATE TABLE t1(a, b);
CREATE TABLE t2(c INTEGER PRIMARY KEY, d);
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)
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)
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)
3. Сложные случаи
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)
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)
4. Оценки планировщика
sqlite> .scanstats est
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)
5. Низкоуровневые данные о профилировании
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