Ускорение запросов COUNT DISTINCT в SQLite - PullRequest
0 голосов
/ 05 октября 2019

Обновление: добавлена ​​справочная информация и более подробное объяснение

С учетом ~ 300K записей в следующей таблице

CREATE TABLE t (
    id INTEGER PRIMARY KEY, 
    a TEXT, 
    b TEXT, 
    c TEXT, 
    deleted INTEGER DEFAULT 0
);

CREATE INDEX ix_t ON t (deleted) WHERE deleted = 0;

1) SELECT Count(id) FROM t WHERE deleted = 0;
2) SELECT Count(DISTINCT a) FROM t WHERE deleted = 0;
3) SELECT Count(DISTINCT b) FROM t WHERE deleted = 0;
4) SELECT Count(DISTINCT c) FROM t WHERE deleted = 0;

Запрос 1 занимает 4-5 мс. Остальные три запроса COUNT (DISTINCT <col>) занимают 600-900 мс. Как я могу ускорить эти запросы в том же порядке, что и первый? Я создал следующие индексы, но это не помогло

CREATE INDEX ix_t_a ON t (a, deleted) WHERE deleted = 0;
-- (and so on the columns b and c as well)

EXPLAIN QUERY PLAN SELECT Count(DISTINCT a) FROM t WHERE deleted = 0;

-- output: SEARCH TABLE t USING INDEX ix_t (deleted=?)

Как мы видим выше, новый индекс не используется.

Столбец deleted является флагом для отслеживаниястрок, которые должны быть исключены из всех запросов. По большей части таких строк будет очень мало, но важно, чтобы они не использовались при подсчете и выборе.

В конечном итоге количество строк может увеличиться в 3 раза, скажем, до 1М. Даже тогда количество deleted строк будет минимальным.

Количество COUNT (DISTINCT column) предназначено для создания фасетов. Допустим, кто-то ищет все строки WHERE a = 'foo'. Мне нужно вернуть совпадающие строки, и мне также нужно вернуть, сколько DISTINCT b и c присутствуют в этих строках. Итак, я сделаю что-то вроде

-- number of rows in the result set
SELECT COUNT(id) FROM t WHERE deleted = 0 AND a = 'foo';

-- the actual result set
SELECT * FROM t WHERE deleted = 0 AND a = 'foo' LIMIT 30 OFFSET 0;

-- facet counts
SELECT COUNT (DISTINCT b) FROM t WHERE deleted = 0 AND a = 'foo';
SELECT COUNT (DISTINCT c) FROM t WHERE deleted = 0 AND a = 'foo';

В самом первом случае, поскольку нет условия WHERE, приведенные выше запросы будут

-- number of rows in the result set
SELECT COUNT(id) FROM t WHERE deleted = 0;

-- the actual result set
SELECT * FROM t WHERE deleted = 0 LIMIT 30 OFFSET 0;

-- facet counts
SELECT COUNT (DISTINCT a) FROM t WHERE deleted = 0;
SELECT COUNT (DISTINCT b) FROM t WHERE deleted = 0;
SELECT COUNT (DISTINCT c) FROM t WHERE deleted = 0;

Фактические столбцы втаблицы, из которых состоят грани, составляют около 8 или 9. Итак, мне нужно сделать около 8 или 9 SELECT COUNT (DISTINCT col). Каждый из них занимает около 600-900 мс, то есть почти 6-10 секунд. Слишком медленно для запроса с точки зрения пользователя. Сокращение числа выборок вдвое или ⅔ имеет огромное значение.

На самом деле у меня также есть кэш запросов, поэтому результаты любого запроса, выполненного один раз, будут кэшироваться, а во второй раз будут очень быстрыми, так какПока это точно такой же запрос. Здесь под запросом я подразумеваю запрос от pov пользователя. Конечно, каждый запрос от pov пользователя приводит к 9-10 запросам к базе данных. Тем не менее, скорость очень важна для создания эффективного приложения.

1 Ответ

2 голосов
/ 05 октября 2019

Как я могу ускорить эти запросы в том же порядке, что и первый?

Часть 1

Я не уверен, что вы можете так много иесли это так, то это может зависеть от самих данных, например, если анализ делает некоторые удивительные вещи.

DISTINCT довольно сильно влияет. Так что это может быть единственной причиной расхождения

Если вы просто используете EXPLAIN для первого запроса без DISTINCT, результат будет: -

enter image description here

Для других запросов результат EXPLAIN выглядит следующим образом: -

enter image description here

Как я могу ускорить эти запросы в том же порядке, что и первый?

Часть 2

Возможно, рассмотрите возможность адаптации следующего кода, который использовалсянемного поиграть и прийти к выводу, что ANALYZE не может улучшить ситуацию, и при этом не кажется, что игра с (немного) с индексами имеет большую разницу: -

DROP TABLE IF EXISTS t;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT, c TEXT, deleted INTEGER DEFAULT 0);

WITH 
    alphabet(letters) AS (SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
    cte1(counter,deleted) AS (SELECT 1, abs(random()) % 2  UNION ALL SELECT counter+1, abs(random()) % 2 FROM cte1 LIMIT 300000)
INSERT INTO t (deleted,a,b,c) 
    SELECT deleted,
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1)
    FROM cte1;

SELECT * FROM t; /* COMMENT/UNCOMMENT WITH/WITHOUT -- To HIDE/SHOW TABLE */ 


CREATE INDEX ix_t ON t (deleted) WHERE deleted = 0;

-- EXPLAIN  /* UNCOMMENT BY REMOVING -- ON THIS LINE TO JUST DO EXPLAIN */
-- QUERY PLAN /* TO DO EXPLAIN QUERY PLAN UNCOMMNET LINE ABOVE AS ABOVE and UNCOMMENT THIS SAME WAY */
SELECT Count(id) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0;
ANALYZE;
SELECT Count(id) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0;

/*<<<<<<<<<< ATTEMPT 2 >>>>>>>>>>*/
DROP TABLE IF EXISTS t;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT, c TEXT, deleted INTEGER DEFAULT 0);

WITH 
    alphabet(letters) AS (SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
    cte1(counter,deleted) AS (SELECT 1, abs(random()) % 2  UNION ALL SELECT counter+1, abs(random()) % 2 FROM cte1 LIMIT 300000)
INSERT INTO t (deleted,a,b,c) 
    SELECT deleted,
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1)
    FROM cte1;

-- SELECT * FROM t;


CREATE INDEX ix_t ON t (deleted,a,b,c) WHERE deleted = 0;

-- EXPLAIN 
-- QUERY PLAN
SELECT Count(id) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0;
ANALYZE;
SELECT Count(id) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0;

Это при запуске (при условии, что использовался инструмент(хорошо в Navicat) позволяет серию запросов). Будет: -

  1. Удаляет и создает таблицу t
  2. Заполняет t случайными данными (кроме строк)
  3. Создает индекс (см. Код, отличающийся от 2-й части)asis)
  4. Запускает 4 запроса (см. комментарии о включении или отключении плана объяснения или объяснения запроса)
  5. Анализирует.
  6. Запускает те же 4 запроса после анализа.
  7. Повторяет 1-6 (с другим индексом).

Пример журнала: -

DROP TABLE IF EXISTS t
> OK
> Time: 1.229s


CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT, c TEXT, deleted INTEGER DEFAULT 0)
> OK
> Time: 0.132s


WITH 
    alphabet(letters) AS (SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
    cte1(counter,deleted) AS (SELECT 1, abs(random()) % 2  UNION ALL SELECT counter+1, abs(random()) % 2 FROM cte1 LIMIT 300000)
INSERT INTO t (deleted,a,b,c) 
    SELECT deleted,
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1)
    FROM cte1
> Affected rows: 300000
> Time: 0.75s


-- SELECT * FROM t;


CREATE INDEX ix_t ON t (deleted) WHERE deleted = 0
> OK
> Time: 0.195s


EXPLAIN 
-- QUERY PLAN
SELECT Count(id) FROM t WHERE deleted = 0
> OK
> Time: 0s


EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0
> OK
> Time: 0s


EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0
> OK
> Time: 0s


EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0
> OK
> Time: 0s


ANALYZE
> OK
> Time: 0.137s


SELECT Count(id) FROM t WHERE deleted = 0
> OK
> Time: 0.031s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0
> OK
> Time: 0.057s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0
> OK
> Time: 0.054s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0
> OK
> Time: 0.055s


/*<<<<<<<<<< ATTEMPT 2 >>>>>>>>>>*/
DROP TABLE IF EXISTS t
> OK
> Time: 0.891s


CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT, c TEXT, deleted INTEGER DEFAULT 0)
> OK
> Time: 0.153s


WITH 
    alphabet(letters) AS (SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
    cte1(counter,deleted) AS (SELECT 1, abs(random()) % 2  UNION ALL SELECT counter+1, abs(random()) % 2 FROM cte1 LIMIT 300000)
INSERT INTO t (deleted,a,b,c) 
    SELECT deleted,
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1)
    FROM cte1
> Affected rows: 300000
> Time: 0.643s


-- SELECT * FROM t;


CREATE INDEX ix_t ON t (deleted,a,b,c) WHERE deleted = 0
> OK
> Time: 0.583s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(id) FROM t WHERE deleted = 0
> OK
> Time: 0.029s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0
> OK
> Time: 0.041s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0
> OK
> Time: 0.029s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0
> OK
> Time: 0.031s


ANALYZE
> OK
> Time: 0.121s


SELECT Count(id) FROM t WHERE deleted = 0
> OK
> Time: 0.038s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM t WHERE deleted = 0
> OK
> Time: 0.046s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM t WHERE deleted = 0
> OK
> Time: 0.029s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM t WHERE deleted = 0
> OK
> Time: 0.031s

Часть 3

У меня нетпроделал большую работу, работая с таймингами, но, возможно, учтите это (используя таблицу соблазнов с только удаленными = 0 строками): -

DROP TABLE IF EXISTS t;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT, c TEXT, deleted INTEGER DEFAULT 0);

WITH 
    alphabet(letters) AS (SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
    cte1(counter,deleted) AS (SELECT 1, abs(random()) % 2  UNION ALL SELECT counter+1, abs(random()) % 2 FROM cte1 LIMIT 300000)
INSERT INTO t (deleted,a,b,c) 
    SELECT deleted,
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1)
    FROM cte1;

-- SELECT * FROM t; /* COMMENT/UNCOMMENT WITH/WITHOUT -- To HIDE/SHOW TABLE */ 

CREATE TEMP TABLE trimmedt AS SELECT * FROM t WHERE deleted = 0;


-- EXPLAIN  /* UNCOMMENT BY REMOVING -- ON THIS LINE TO JUST DO EXPLAIN */
-- QUERY PLAN /* TO DO EXPLAIN QUERY PLAN UNCOMMNET LINE ABOVE AS ABOVE and UNCOMMENT THIS SAME WAY */
SELECT Count(id) FROM trimmedt;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM trimmedt;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM trimmedt;
-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM trimmedt;

Журнал сообщений: -

DROP TABLE IF EXISTS t
> OK
> Time: 1.242s


CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT, c TEXT, deleted INTEGER DEFAULT 0)
> OK
> Time: 0.103s


WITH 
    alphabet(letters) AS (SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
    cte1(counter,deleted) AS (SELECT 1, abs(random()) % 2  UNION ALL SELECT counter+1, abs(random()) % 2 FROM cte1 LIMIT 300000)
INSERT INTO t (deleted,a,b,c) 
    SELECT deleted,
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1),
        substr((SELECT letters FROM alphabet),(abs(random()) % ((SELECT max(length(letters)) FROM alphabet)-10)) + 1,(abs(random()) % 10) + 1)
    FROM cte1
> Affected rows: 300000
> Time: 0.722s


-- SELECT * FROM t; /* COMMENT/UNCOMMENT WITH/WITHOUT -- To HIDE/SHOW TABLE */ 

CREATE TEMP TABLE trimmedt AS SELECT * FROM t WHERE deleted = 0
> OK
> Time: 0.091s


-- EXPLAIN  /* UNCOMMENT BY REMOVING -- ON THIS LINE TO JUST DO EXPLAIN */
-- QUERY PLAN /* TO DO EXPLAIN QUERY PLAN UNCOMMNET LINE ABOVE AS ABOVE and UNCOMMENT THIS SAME WAY */
SELECT Count(id) FROM trimmedt
> OK
> Time: 0.009s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT a) FROM trimmedt
> OK
> Time: 0.03s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT b) FROM trimmedt
> OK
> Time: 0.03s


-- EXPLAIN 
-- QUERY PLAN
SELECT Count(DISTINCT c) FROM trimmedt
> OK
> Time: 0.031s
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...