Как оптимизировать количество и порядок по запросу в миллионах строк - PullRequest
0 голосов
/ 08 июня 2018

Требуется помощь в оптимизации порядка и количества запросов, у меня есть таблицы, содержащие миллионы (около 3 миллионов) строк.

Мне нужно объединить 4 таблицы и извлечь записи. Когда я запускаю простой запрос, он требуеттолько миллисекунда для завершения, но когда я пытаюсь сосчитать или упорядочить, оставив таблицу соединений, она застрянет на неограниченное время.

Пожалуйста, смотрите случаи ниже.

Конфигурация сервера БД:

CPU Number of virtual cores: 4
Memory(RAM): 16 GiB
Network Performance: High

Строки в каждой таблице:

tbl_customers -  #Rows: 20 million.
tbl_customers_address -  #Row 25 million.
tbl_shop_setting - #Rows 50k
aio_customer_tracking - #Rows 5k

Схема таблиц:

CREATE TABLE `tbl_customers` (
    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    `shopify_customer_id` BIGINT(20) UNSIGNED NOT NULL,
    `shop_id` BIGINT(20) UNSIGNED NOT NULL,
    `email` VARCHAR(225) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
    `accepts_marketing` TINYINT(1) NULL DEFAULT NULL,
    `first_name` VARCHAR(50) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
    `last_name` VARCHAR(50) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
    `last_order_id` BIGINT(20) NULL DEFAULT NULL,
    `total_spent` DECIMAL(12,2) NULL DEFAULT NULL,
    `phone` VARCHAR(20) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
    `verified_email` TINYINT(4) NULL DEFAULT NULL,
    `updated_at` DATETIME NULL DEFAULT NULL,
    `created_at` DATETIME NULL DEFAULT NULL,
    `date_updated` DATETIME NULL DEFAULT NULL,
    `date_created` DATETIME NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `shopify_customer_id_unique` (`shopify_customer_id`),
    INDEX `email` (`email`),
    INDEX `shopify_customer_id` (`shopify_customer_id`),
    INDEX `shop_id` (`shop_id`)
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;


CREATE TABLE `tbl_customers_address` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `customer_id` BIGINT(20) NULL DEFAULT NULL,
    `shopify_address_id` BIGINT(20) NULL DEFAULT NULL,
    `shopify_customer_id` BIGINT(20) NULL DEFAULT NULL,
    `first_name` VARCHAR(50) NULL DEFAULT NULL,
    `last_name` VARCHAR(50) NULL DEFAULT NULL,
    `company` VARCHAR(50) NULL DEFAULT NULL,
    `address1` VARCHAR(250) NULL DEFAULT NULL,
    `address2` VARCHAR(250) NULL DEFAULT NULL,
    `city` VARCHAR(50) NULL DEFAULT NULL,
    `province` VARCHAR(50) NULL DEFAULT NULL,
    `country` VARCHAR(50) NULL DEFAULT NULL,
    `zip` VARCHAR(15) NULL DEFAULT NULL,
    `phone` VARCHAR(20) NULL DEFAULT NULL,
    `name` VARCHAR(50) NULL DEFAULT NULL,
    `province_code` VARCHAR(5) NULL DEFAULT NULL,
    `country_code` VARCHAR(5) NULL DEFAULT NULL,
    `country_name` VARCHAR(50) NULL DEFAULT NULL,
    `longitude` VARCHAR(250) NULL DEFAULT NULL,
    `latitude` VARCHAR(250) NULL DEFAULT NULL,
    `default` TINYINT(1) NULL DEFAULT NULL,
    `is_geo_fetched` TINYINT(1) NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`),
    INDEX `customer_id` (`customer_id`),
    INDEX `shopify_address_id` (`shopify_address_id`),
    INDEX `shopify_customer_id` (`shopify_customer_id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;

CREATE TABLE `tbl_shop_setting` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,   
    `shop_name` VARCHAR(300) NOT NULL COLLATE 'latin1_swedish_ci',
     PRIMARY KEY (`id`),
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;


CREATE TABLE `aio_customer_tracking` (
    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    `shopify_customer_id` BIGINT(20) UNSIGNED NOT NULL,
    `email` VARCHAR(255) NULL DEFAULT NULL,
    `shop_id` BIGINT(20) UNSIGNED NOT NULL,
    `domain` VARCHAR(255) NULL DEFAULT NULL,
    `web_session_count` INT(11) NOT NULL,
    `last_seen_date` DATETIME NULL DEFAULT NULL,
    `last_contact_date` DATETIME NULL DEFAULT NULL,
    `last_email_open` DATETIME NULL DEFAULT NULL,
    `created_date` DATETIME NOT NULL,
    `is_geo_fetched` TINYINT(1) NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`),
    INDEX `shopify_customer_id` (`shopify_customer_id`),
    INDEX `email` (`email`),
    INDEX `shopify_customer_id_shop_id` (`shopify_customer_id`, `shop_id`),
    INDEX `last_seen_date` (`last_seen_date`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;

Варианты запросов, которые выполняются и не выполняются:

1. Running:  Below query fetch the records by joining all the 4 tables, It takes only 0.300 ms.

SELECT `c`.first_name,`c`.last_name,`c`.email, `t`.`last_seen_date`, `t`.`last_contact_date`, `ssh`.`shop_name`, ca.`company`, ca.`address1`, ca.`address2`, ca.`city`, ca.`province`, ca.`country`, ca.`zip`, ca.`province_code`, ca.`country_code`
FROM `tbl_customers` AS `c`
JOIN `tbl_shop_setting` AS `ssh` ON c.shop_id = ssh.id 
LEFT JOIN (SELECT shopify_customer_id, last_seen_date, last_contact_date FROM aio_customer_tracking GROUP BY shopify_customer_id) as t ON t.shopify_customer_id = c.shopify_customer_id
LEFT JOIN `tbl_customers_address` as ca ON (c.shopify_customer_id = ca.shopify_customer_id AND ca.default = 1)
GROUP BY c.shopify_customer_id
LIMIT 20

2. Not running: Simply when try to get the count of these row stuk the query, I waited 10 min but still running.

SELECT 
     COUNT(DISTINCT c.shopify_customer_id)   -- what makes #2 different
FROM `tbl_customers` AS `c`
JOIN `tbl_shop_setting` AS `ssh` ON c.shop_id = ssh.id 
LEFT JOIN (SELECT shopify_customer_id, last_seen_date, last_contact_date FROM aio_customer_tracking GROUP BY shopify_customer_id) as t ON t.shopify_customer_id = c.shopify_customer_id
LEFT JOIN `tbl_customers_address` as ca ON (c.shopify_customer_id = ca.shopify_customer_id AND ca.default = 1)
GROUP BY c.shopify_customer_id
LIMIT 20


3. Not running: In the #1 query we simply put the 1 Order by clause and it get stuck, I waited 10 min but still running. I study query optimization some article and tried by indexing, Right Join etc.. but still not working.

SELECT `c`.first_name,`c`.last_name,`c`.email, `t`.`last_seen_date`, `t`.`last_contact_date`, `ssh`.`shop_name`, ca.`company`, ca.`address1`, ca.`address2`, ca.`city`, ca.`province`, ca.`country`, ca.`zip`, ca.`province_code`, ca.`country_code`
FROM `tbl_customers` AS `c`
JOIN `tbl_shop_setting` AS `ssh` ON c.shop_id = ssh.id 
LEFT JOIN (SELECT shopify_customer_id, last_seen_date, last_contact_date FROM aio_customer_tracking GROUP BY shopify_customer_id) as t ON t.shopify_customer_id = c.shopify_customer_id
LEFT JOIN `tbl_customers_address` as ca ON (c.shopify_customer_id = ca.shopify_customer_id AND ca.default = 1)
GROUP BY c.shopify_customer_id
  ORDER BY `t`.`last_seen_date`    -- what makes #3 different
LIMIT 20

ОБЪЯСНЯТЬ ЗАПРОС# 1: enter image description here

EXPLAIN QUERY # 2: enter image description here

EXPLAIN QUERY # 3: enter image description here

Приветствуются любые предложения по оптимизации запроса, структура таблицы.

ЧТО Я ПЫТАЮСЯ СДЕЛАТЬ:

tbl_customers таблица содержит информацию о клиенте, *Таблица 1039 * содержит адреса клиентов (один клиент может иметь несколько адресов), а таблица aio_customer_tracking содержит записи посещений тКлиент last_seen_date является датой посещения.

Теперь я просто хочу получить и подсчитать клиентов, указав их один из адресов и информацию о посещении.Кроме того, я могу заказать по любому из столбцов из этих 3 таблиц. В моем примере я упорядочиваю по last_seen_date (порядок по умолчанию).Надеюсь, это объяснение поможет понять, что я пытаюсь сделать.

Ответы [ 4 ]

0 голосов
/ 15 июня 2018

Запрос 2 содержит логическую ошибку, на которую указывали другие: count(distinct(c.shopify_customer_id)) вернет одно значение, поэтому ваша группа только усложняет запрос (это может действительно сделать группировку MySQL сначала shopify_customer_id, а затем выполнить count(distinct(shopify_customer_id )) что может быть причиной столь длительного времени выполнения

Невозможно оптимизировать порядок запросов Query 3, поскольку вы присоединяетесь к подвыбору, который не может быть проиндексирован. Время, которое требуется, - это просто время, которое системанеобходимо упорядочить набор результатов.

Решение вашей проблемы будет следующим:

  1. изменить индекс shopify_customer_id (shopify_customer_id) таблицы tbl_customers_address на shopify_customer_id (shopify_customer_id, default) для оптимизации следующего запроса

  2. создать таблицу с результатом из запроса 1 (результат), но без

    LEFT JOIN (SELECT shopify_customer_id, last_seen_date, last_contact_date FROM aio_customer_tracking GROUP BY shopify_customer_id) as t ON t.shopify_customer_id = c.shopify_customer_id.

  3. изменить таблицу результатов и добавить столбец для last_seen_date и индексы для last_seen_date и shopify_customer_id

  4. создать ввозможность для результата этого запроса (last_Date):

SELECT shopify_customer_id, last_seen_date, last_contact_date FROM aio_customer_tracking GROUP BY shopify_customer_id

Обновите таблицу результатов значениями из таблицы last_Date

Теперь вы можете выполнить запрос к таблице результатов, упорядоченной функцией last_Date, используя созданный вами индекс.

Весь процесс должен занимать намного меньше времени, чем выполнение запроса 2 или запроса 3

0 голосов
/ 11 июня 2018

В запросе № 1, но не в двух других, оптимизатор может использовать

UNIQUE INDEX `shopify_customer_id_unique` (`shopify_customer_id`)

для сокращения запроса на

GROUP BY c.shopify_customer_id
LIMIT 20

Это потому, что он может остановиться после 20элементы индекса.Запрос не является сверхбыстрым из-за производной таблицы (подзапрос t), которая достигает примерно 51 тыс. Строк.

Запрос №2 может быть медленным просто из-за того, что оптимизатору не удалось заметить и удалить избыточную DISTINCT,Вместо этого он может думать, что не может остановиться после 20.

Запрос # 3 должен пройти полностью через таблицу c, чтобы получить каждую shopify_customer_id группу,Это связано с тем, что ORDER BY предотвращает короткое замыкание до LIMIT 20.

Столбцы в GROUP BY должны включать в себя все неагрегированные столбцы в SELECT, за исключением тех, которые являются уникальнымиопределяется группой по столбцам.Поскольку вы сказали, что для одного shopify_customer_id может быть несколько адресов, выборка ca.address1 не является правильной в связи с GROUP BY shopify_customer_id.Точно так же подзапрос кажется неправильным в отношении last_seen_date, last_contact_date.

В aio_customer_tracking это изменение (к «покрывающему» индексу) может немного помочь:

INDEX (`shopify_customer_id`)

на

INDEX (`shopify_customer_id`, `last_seen_date`, `last_contact_date`)

Рассечение цели

Теперь просто хочу ... посчитать клиентов

Кпосчитайте клиентов, сделайте это, но не пытайтесь объединить это с «извлечением»:

SELECT COUNT(*) FROM tbl_customers;

Теперь просто хочу получить ... клиентов ...

tbl_customers - #Rows: 20 млн.

Конечно, вы не хотите получать 20 миллионов строк!Я не хочу думать о том, как попытаться это сделать.Просьба уточнить.И я не приму нумерацию страниц в таком количестве строк.Возможно, есть пункт WHERE ??Предложение WHERE (обычно) является наиболее важной частью оптимизации!

Теперь я просто хочу получить ... клиентов, с одним из их адресов и информацией о посещении.

Предполагая, что WHERE отфильтровывает «несколько» клиентов, затем JOINing к другой таблице, чтобы получить «любой» адрес и «любую» информацию о посещении, может быть проблематичным и / или неэффективным,Требовать «первый» или «последний» вместо «любой» будет не так-то просто, но может оказаться более значимым.

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

Кроме того, я могу заказать по любому из столбцов из этих 3 таблиц. В моем примере я упорядочиваю по last_seen_date (порядок по умолчанию).

Давайте сосредоточимся на оптимизации WHERE, затем добавим last_seen_date в конец любого индекса.

0 голосов
/ 13 июня 2018

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

Кроме того, удаляет оператор GROUP BY.

Еще можно сказать о правильном использовании кластеризованных и некластеризованных индексов, GROUP BY, ORDER BY, WHERE и представлений., для оптимизации запросов.Тем не менее, я думаю, что если вы удалите некоторые индексы, ваши запросы значительно ускорятся.(Возможно, также переработайте ваши запросы, чтобы следовать более строгим стандартам SQL и быть немного более логичным, но это выходит за рамки этого вопроса.)

Еще одна вещь - что вы делаете с результатами запроса?Хранится ли это где-то и доступно для поиска, используется для расчетов, используется для автоматических отчетов, отображения через соединение с веб-базой данных и т. Д.?Это имеет значение, потому что если вам просто нужен отчет / резервная копия или экспорт в плоский файл, то есть более эффективные способы получения этих данных.Множество разных опций в зависимости от того, что вы делаете.

0 голосов
/ 11 июня 2018

shopify_customer_id уникален в таблице tbl_customers, затем во втором запросе, почему вы используете различные и группировать по столбцу shopify_customer_id?

Пожалуйста, избавьтесь от этого.

...