Как я могу улучшить производительность запросов для более 200 миллионов записей - PullRequest
8 голосов
/ 13 июня 2019

Фон

У меня есть тестовая среда MySQL с таблицей, которая содержит более 200 миллионов строк.По этой таблице необходимо выполнить два типа запросов;

  1. Есть ли определенные строки.Учитывая client_id и список sgtin с, которые могут содержать до 50 000 элементов, мне нужно знать, какие sgtin с присутствуют в таблице.
  2. Выберите эти строки.Учитывая client_id и список sgtin с, которые могут вместить до 50 000 элементов, мне нужно получить полный ряд.(store, gtin ...)

Таблица может увеличиться до 200+ миллионов записей для одного 'client_id'.

Тестовая среда

Xeon E3-1545M / 32GB RAM / SSD.InnoDB буферный пул 24 ГБ.(Производством будет сервер большего размера с 192 ГБ ОЗУ)

Таблица

CREATE TABLE `sgtins` (
  `client_id` INT UNSIGNED NOT NULL,
  `sgtin` varchar(255) NOT NULL,
  `store` varchar(255) NOT NULL,
  `gtin` varchar(255) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX (`client_id`, `store`, `sgtin`),
  INDEX (`client_id`),
  PRIMARY KEY (`client_id`,`sgtin`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Тесты

Первый случайный случайЗначения sgtin распределены по 10 'client_id's, чтобы заполнить таблицу 200 миллионами строк.

Я создал инструмент для тестирования, который выполняет различные запросы, которые я пробовал.Также я использовал план объяснения, чтобы выяснить, какая производительность лучше.Этот инструмент будет читать для каждого теста новые случайные данные из данных, которые я использовал для заполнения базы данных.Чтобы убедиться, что каждый запрос отличается.

Для этого поста я буду использовать 28 sgtin с. Временная таблица

CREATE TEMPORARY TABLE sgtins_tmp_table (`sgtin` varchar(255) primary key)
 engine=MEMORY;

Существующий запрос

Я использую этот запрос, чтобы выяснить, существуют ли sgtin s.Также это самый быстрый запрос, который я нашел.Для 50K sgtin с этот запрос займет от 3 до 9 секунд.

-- cost = 17 for 28 sgtins loaded in the temp table.
SELECT sgtin
FROM sgtins_tmp_table
WHERE EXISTS 
  (SELECT sgtin FROM sgtins 
  WHERE sgtins.client_id = 4 
  AND sgtins.sgtin = sgtins_tmp_table.sgtin);

Explain plan

Выбор запросов

-- cost = 50.60 for 28 sgtins loaded in the temp table. 50K not usable.
SELECT sgtins.sgtin, sgtins.store, sgtins.timestamp
FROM sgtins_tmp_table, sgtins
WHERE sgtins.client_id = 4
AND sgtins_tmp_table.sgtin = sgtins.sgtin;

Explain plan

-- cost = 64 for 28 sgtins loaded in the temp table.
SELECT sgtins.sgtin, sgtins.store, sgtins.timestamp
FROM sgtins
WHERE sgtins.client_id = 4
AND sgtins.sgtin IN ( SELECT sgtins_tmp_table.sgtin
 FROM sgtins_tmp_table);

Explain plan

-- cost = 50.60 for 28 sgtins loaded in the temp table.
SELECT sgtins_tmp_table.epc, sgtins.store
FROM sgtins_tmp_table, sgtins
WHERE exists (SELECT organization_id, sgtin FROM sgtins WHERE client_id = 4 AND sgtins.sgtin = sgtins_tmp_table.sgtin)
AND sgtins.client_id = 4
AND sgtins_tmp_table.sgtin = sgtins.sgtin;

Explain plan

Сводка

Существующий запрос пригоден для использования, но выбор выполняется медленно.Что я могу с этим поделать?И любые советы приветствуются:)

Ответы [ 3 ]

2 голосов
/ 13 июня 2019

Я бы написал ваш exists запрос так:

SELECT stt.sgtin
FROM sgtins_tmp_table stt
WHERE EXISTS (SELECT 1
              FROM sgtins s
              WHERE s.client_id = 4 AND
                    s.sgtin = stt.sgtin
             );

Для этого запроса вы хотите индекс на sgtins(sgtin, client_id).

1 голос
/ 14 июня 2019

Предполагая, что 200M строк и не более 50K sgtins на одного клиента, должно быть более 4K клиентов?

Сравнение только с 10 клиентами рискованно. Оптимизатор переключается между использованием индекса и выполнением сканирования таблицы в определенных случаях; это может быть такой случай.

Итак, пожалуйста, сформулируйте конечную цель; Я не хочу советовать вам, как ускорить тест, только чтобы «реальный» случай не работал с рекомендациями.

Кроме того, список stgins статичен? Вы подразумевали это, предлагая предварительно создать таблицу MEMORY. Но это кажется необычным. Возможно, «реальному» случаю каждый раз дается другой набор sgtins.

Итак, я отвечу на этот вопрос:

  • 200M строк
  • Таблица больше 24ГБ
  • innodb_buffer_pool_size = 24G
  • Тысячи различных client_id значений. (При наличии только 10 оптимизатор склонен игнорировать индексы и выполнять сканирование таблицы.)
  • Тысячи stgin значений для каждого client_id
  • Пара (client_id, stgin) уникальна
  • Каждый запрос может иметь другой список stgins; то есть не может принимать один и тот же список stgins от запуска к запуску
  • Хотите оптимизировать что-то вроде SELECT stgin FROM t WHERE client_id = 1234 AND stgin IN (..long list..)
  • Хотите оптимизировать что-то вроде SELECT * FROM t WHERE client_id = 1234 AND stgin IN (..long list..)

Независимо от чисел, предоставленных EXPLAIN, следующее решение является оптимальным для обоих запросов:

WHERE client_id = 1234 AND stgin IN (..long list..)`
PRIMARY KEY(client_id, stgin)   -- in this order.

Почему?

  • Оптимизатор рад сосредоточиться на client_id = constant и пролистать список stgins.
  • Имея client_id first в PK, все действия для SELECT будут сосредоточены на небольшой части таблицы. Это важно, потому что оно ограничивает количество блоков для прикосновения меньше, чем buffer_pool_size.
  • Технически независимый INDEX(client_id, stgin) был бы быстрее для SELECT stgin..., но я не рекомендую его, поскольку он настолько избыточен и не сильно экономит производительность.

Комментарии к анализу затрат:

  • Не учитывается, кэшированы ли блоки или нет. С жесткими дисками это может иметь огромное (10x) значение.
  • Он не принимает во внимание ни индекс против данных, ни индекс + данные (как в непокрывающем вторичном индексе)
  • Он ничего не знает о распределении значений. (кроме случаев использования MariaDB или MySQL 8.0 с гистограммами)
1 голос
/ 13 июня 2019

Я бы посоветовал переписать ваш EXISTS SQL, так как связанные подзапросы имеют тенденцию плохо оптимизировать время от времени.Предлагаемый запрос будет использовать вместо INNER JOIN.

SELECT filter.sgtin
FROM (SELECT '<value>' AS sgtin UNION ALL SELECT '<value>' ..) AS filter
INNER JOIN sgtins ON filter.sgtin = sgtins.sgtin WHERE sgtins.client_id = 4

Скорее всего, это быстрее, чем использование временной таблицы.Но вы имеете дело со значениями 50 КБ, поэтому я бы имел смысл сгенерировать необходимую производную таблицу SQL с динамическим SQL непосредственно из временной таблицы.

Также как я предложил в чате.Создание индекса (sgtins, client_id), скорее всего, имеет больше смысла в зависимости от избирательности данных, которая не совсем понятна.Поскольку этот индекс может ускорить выполнение вашего подзапроса.

Запрос

# Maybe also needed to be changed with 50 K 
# SET SESSION max_allowed_packet = ??; 


# needed for GROUP_CONCAT as if defualts to only 1024 
SET SESSION group_concat_max_len = @@max_allowed_packet;

SET @UNION_SQL = NULL;

SELECT
  CONCAT(
       'SELECT '
    ,  GROUP_CONCAT(
          CONCAT("'", sgtins_tmp_table.sgtin,"'", ' AS sgtin')
          SEPARATOR ' UNION ALL SELECT '
       )
  )
FROM
 sgtins_tmp_table
INTO
 @UNION_SQL;


SET @SQL = CONCAT("
SELECT filter.sgtin
FROM (",@UNION_SQL,") AS filter
INNER JOIN sgtins ON filter.sgtin = sgtins.sgtin WHERE sgtins.client_id = 4
");


PREPARE q FROM @SQL;
EXECUTE q;

см. демо

Изменено из-за комментариев

Более идеальным подходом было бы использование фиксированной таблицы, которую вы индексируете, и использование CONNECTION_ID() для разделения значений поиска.

CREATE TABLE sgtins_filter (
    connection_id INT
  , sgtin varchar(255) NOT NULL
  , INDEX(connection_id, sgtin)
);

Тогда вы можете просто объединить обе таблицы

SELECT sgtins_filter.sgtin
FROM sgtins_filter
INNER JOIN sgtins
ON
    sgtins_filter.sgtin = sgtins.sgtin
  AND
    sgtins_filter.connection_id = CONNECTION_ID()
  AND 
    sgtins.client_id = 4; 

см. демо

...