У меня проблема с производительностью в SQLite с SELECT COUNT (*) для больших таблиц.
Поскольку я еще не получил полезного ответа и провел дополнительное тестирование, я отредактировал свой вопрос, включив в него свои новые выводы.
У меня есть 2 таблицы:
CREATE TABLE Table1 (
Key INTEGER NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL,
CONSTRAINT PK_Table1 PRIMARY KEY (Key ASC))
CREATE Table2 (
Key INTEGER NOT NULL,
Key2 INTEGER NOT NULL,
... a few other fields ...,
CONSTRAINT PK_Table2 PRIMARY KEY (Key ASC, Key2 ASC))
В Table1 содержится около 8 миллионов записей, в Table2 - около 51 миллиона записей, а размер файла базы данных превышает 5 ГБ.
Таблица1 имеет еще 2 индекса:
CREATE INDEX IDX_Table1_Status ON Table1 (Status ASC, Key ASC)
CREATE INDEX IDX_Table1_Selection ON Table1 (Selection ASC, Key ASC)
Поле «Состояние» является обязательным, но имеет только 6 различных значений, «Выбор» не требуется и имеет только около 1,5 миллиона значений, отличных от нуля, и только около 600 000 различных значений.
Я провел несколько тестов для обеих таблиц, вы можете увидеть сроки ниже, и я добавил «план запроса объяснения» для каждого запроса (QP). Я поместил файл базы данных на USB-карту памяти, чтобы можно было удалять его после каждого теста и получать надежные результаты без вмешательства в кэш диска. Некоторые запросы на USB выполняются быстрее (я полагаю, из-за отсутствия времени поиска), но некоторые - медленнее (сканирование таблицы).
SELECT COUNT(*) FROM Table1
Time: 105 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
Time: 153 sec
QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Key = 5123456
Time: 5 ms
QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 16 sec
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
SELECT * FROM Table1 WHERE Selection = 'SomeValue' AND Key > 5123456 LIMIT 1
Time: 9 ms
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Selection (Selection=?) (~3 rows)
Как видите, счетчик очень медленный, но нормальный выбор быстрый (за исключением второго, который занял 16 секунд).
То же самое относится и к таблице 2:
SELECT COUNT(*) FROM Table2
Time: 528 sec
QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~1000000 rows)
SELECT COUNT(Key) FROM Table2
Time: 249 sec
QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table2 WHERE Key = 5123456 AND Key2 = 0
Time: 7 ms
QP: SEARCH TABLE Table2 USING INDEX sqlite_autoindex_Table2_1 (Key=? AND Key2=?) (~1 rows)
Почему SQLite не использует автоматически созданный индекс для первичного ключа в Table1?
И почему, когда он использует автоиндекс на Table2, это все равно занимает много времени?
Я создал те же таблицы с тем же содержимым и индексами в SQL Server 2008 R2, и их число практически мгновенно.
В одном из комментариев ниже предлагается выполнить АНАЛИЗ в базе данных. Я сделал, и это заняло 11 минут.
После этого я снова запустил некоторые тесты:
SELECT COUNT(*) FROM Table1
Time: 104 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~7848023 rows)
SELECT COUNT(Key) FROM Table1
Time: 151 sec
QP: SCAN TABLE Table1 (~7848023 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 5 ms
QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid>?) (~196200 rows)
SELECT COUNT(*) FROM Table2
Time: 529 sec
QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~51152542 rows)
SELECT COUNT(Key) FROM Table2
Time: 249 sec
QP: SCAN TABLE Table2 (~51152542 rows)
Как видите, запросы выполнялись в одно и то же время (за исключением того, что план запроса теперь отображает реальное количество строк), теперь также выполняется только более медленный выбор.
Далее я создаю дополнительный индекс dan в поле Key таблицы Table1, который должен соответствовать автоиндексу. Я сделал это на исходной базе данных, без данных анализа. Создание этого индекса заняло более 23 минут (помните, это на USB-карте).
CREATE INDEX IDX_Table1_Key ON Table1 (Key ASC)
Затем я снова запустил тесты:
SELECT COUNT(*) FROM Table1
Time: 4 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Key(~1000000 rows)
SELECT COUNT(Key) FROM Table1
Time: 167 sec
QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 17 sec
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
Как видите, индекс помог с количеством (*), но не с количеством (ключом).
Наконец, я создал таблицу, используя ограничение столбца вместо ограничения таблицы:
CREATE TABLE Table1 (
Key INTEGER PRIMARY KEY ASC NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL)
Затем я снова запустил тесты:
SELECT COUNT(*) FROM Table1
Time: 6 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
Time: 28 sec
QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 10 sec
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
Хотя планы запросов одинаковы, времена намного лучше. Почему это?
Проблема в том, что ALTER TABLE не позволяет конвертировать существующую таблицу, и у меня есть много существующих баз данных, которые я не могу преобразовать в эту форму. Кроме того, использование ограничения столбца вместо ограничения таблицы не будет работать для Table2.
Кто-нибудь знает, что я делаю неправильно и как решить эту проблему?
Я использовал System.Data.SQLite версии 1.0.74.0 для создания таблиц и запуска тестов. Я использовал SQLiteSpy 1.9.1.
Спасибо
Марк