Mysql выберите запрос с подзапросами с индексом, требующим много времени для извинения (28 se c) - PullRequest
2 голосов
/ 20 марта 2020

Я пытаюсь выполнить запрос ниже MYsql, запрос выполняется слишком долго. Мне нужно извлечь для каждого поставщика:

  • total_purchases
  • total_sales

Если у поставщика начальный баланс отличается от нуля, используйте его в предложении where в подзапросы, в противном случае используйте '2020-01-01'

Вот запрос, который я использую:

SELECT 
sup.name AS supplier_name,
sup.id AS supplier_id,
sup.opening_balance_date,
sup.opening_balance AS opening_balance,
(
    SELECT
    IFNULL(SUM(pop.quantity * pop.cost),0)   AS total_purchases
    FROM purchase_order_products pop
    JOIN purchase_order po ON
    pop.purchase_order_id = po.id
    WHERE
    DATE(po.created_at) >= IFNULL(sup.opening_balance_date, '2020-01-01')  
    AND DATE(po.created_at) < '2020-03-01'
    AND po.status = 'approved'
    AND po.supplier_id = sup.id
) as total_purchases,
(
    SELECT 
    IFNULL(sum(soi.total_cost),0) AS total_sales              
    FROM
    sales_order_item soi 
    JOIN sales_order so use index (date_status_completed) ON
    so.id = soi.sales_order_id
    WHERE
    soi.total_cost > 0
    AND soi.supplier_id = sup.id
    AND so.order_status = 'complete' 
    AND so.completed_returned = 0
    AND so.desired_delivery_date >= IFNULL(sup.opening_balance_date, '2020-01-01')   
) AS total_sales
FROM supplier sup
WHERE sup.is_active = 1
group by sup.id
ORDER BY sup.name;

Если я выполню запрос без указанного ниже подзапроса, он получит 0.5 se c который принят.

(
SELECT 
IFNULL(sum(soi.total_cost),0) AS total_sales              
FROM
sales_order_item soi 
JOIN sales_order so use index (date_status_completed) ON
so.id = soi.sales_order_id
WHERE
soi.total_cost > 0
AND soi.supplier_id = sup.id
AND so.order_status = 'complete' 
AND so.completed_returned = 0
AND so.desired_delivery_date >= IFNULL(sup.opening_balance_date, '2020-01-01')   
) AS total_sales

Я думаю, что проблема здесь: soi.supplier_id = sup.id

вот объяснение enter image description here

и здесь база данных enter image description here

Создать запрос:

--
-- Table structure for table `purchase_order`
--

CREATE TABLE `purchase_order` (
  `id` int(11) NOT NULL,
  `supplier_id` int(11) NOT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `status` varchar(150) DEFAULT 'Pending',
  `total` float NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `purchase_order_products`
--

CREATE TABLE `purchase_order_products` (
  `id` int(11) NOT NULL,
  `purchase_order_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `quantity` int(50) NOT NULL,
  `cost` decimal(15,3) NOT NULL,
  `reporting_quantity` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------


--
-- Table structure for table `sales_order`
--

CREATE TABLE `sales_order` (
  `id` varchar(20) NOT NULL,
  `order_status` varchar(50) DEFAULT NULL,
  `desired_delivery_date` date DEFAULT NULL,
  `completed_returned` int(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `sales_order_item`
--

CREATE TABLE `sales_order_item` (
  `id` varchar(20) NOT NULL,
  `sales_order_id` varchar(20) NOT NULL,
  `total_cost` float NOT NULL DEFAULT '0',
  `supplier_id` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `supplier`
--

CREATE TABLE `supplier` (
  `id` int(11) NOT NULL,
  `name` varchar(400) NOT NULL,
  `opening_balance` decimal(10,2) NOT NULL DEFAULT '0.00',
  `opening_balance_date` date DEFAULT NULL,
  `is_active` tinyint(4) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------


--
-- Indexes for dumped tables
--

--
-- Indexes for table `credit_note`

--
-- Indexes for table `purchase_order`
--
ALTER TABLE `purchase_order`
  ADD PRIMARY KEY (`id`),
  ADD KEY `id` (`id`),
  ADD KEY `supplier_id` (`supplier_id`),
  ADD KEY `created_at` (`created_at`,`status`),
  ADD KEY `supplier_id_2` (`supplier_id`,`created_at`),
  ADD KEY `id_date_status` (`supplier_id`,`created_at`,`status`) USING BTREE;

--
-- Indexes for table `purchase_order_products`
--
ALTER TABLE `purchase_order_products`
  ADD PRIMARY KEY (`id`),
  ADD KEY `purchase_order_id` (`purchase_order_id`),
  ADD KEY `product_id` (`product_id`),
  ADD KEY `cost` (`cost`,`reporting_quantity`);

--
-- Indexes for table `sales_order`
--
ALTER TABLE `sales_order`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `idx_id` (`id`),
  ADD KEY `idx_desired_delivery_date` (`desired_delivery_date`),
  ADD KEY `date_status_completed` (`order_status`,`desired_delivery_date`,`completed_returned`) USING BTREE,
  ADD KEY `completed_returned` (`completed_returned`),
  ADD KEY `order_status` (`order_status`),
  ADD KEY `order_status_2` (`order_status`,`completed_returned`);

--
-- Indexes for table `sales_order_item`
--
ALTER TABLE `sales_order_item`
  ADD PRIMARY KEY (`id`),
  ADD KEY `sales_order_id` (`sales_order_id`),
  ADD KEY `reporting_supplier` (`supplier_id`) USING BTREE,
  ADD KEY `total_cost` (`total_cost`),
  ADD KEY `supplier_cost` (`supplier_id`,`total_cost`) USING BTREE,
  ADD KEY `supplier_id` (`supplier_id`);

--
-- Indexes for table `supplier`
--
ALTER TABLE `supplier`
  ADD PRIMARY KEY (`id`),
  ADD KEY `id` (`id`),
  ADD KEY `is_active` (`is_active`);


--
-- AUTO_INCREMENT for table `purchase_order`
--
ALTER TABLE `purchase_order`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=26763;
--
-- AUTO_INCREMENT for table `purchase_order_products`
--
ALTER TABLE `purchase_order_products`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=41884;

--
-- AUTO_INCREMENT for table `supplier`
--
ALTER TABLE `supplier`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=182;

Ответы [ 3 ]

1 голос
/ 23 марта 2020

A PRIMARY KEY - это уникальный ключ:

ADD PRIMARY KEY (`id`),
ADD KEY `id` (`id`),  -- Toss this; it is redundant

Не смешивайте INT и VARCHAR:

so.id = soi.sales_order_id

Не используйте DATE(), когда вы не нужно:

DATE(po.created_at) < '2020-03-01'

->

po.created_at < '2020-03-01'

Если у вас INDEX(a,b), вам также не нужно INDEX(a).

Некоторые из вышеперечисленных могут серьезно повлиять на производительность.

Вот несколько составных индексов, которые могут помочь:

sup:  (is_active, id)
so:  (completed_returned, order_status, desired_delivery_date, id)
soi:  (supplier_id, total_cost, sales_order_id)
po:  (supplier_id, status, created_at, id)
0 голосов
/ 25 марта 2020

Редакция к запросу, проверенному на mysql версии 8.0.19, при условии, что таблицы purchase_order и sales_order_item являются разреженными, но обе ( должны иметь ссылку FOREIGN KEY ) таблица supplier (но не обязательно одна и та же записей у поставщика).

  • Вы можете сократить количество необходимых проверок, нажав одну из purchase_order или sales_order_item в один подзапрос EXISTS, а затем преобразует другой в ЛЕВЫЕ ВНЕШНИЕ СОЕДИНЕНИЯ.
  • Функциональность SUM () устраняет необходимость операции нуль-объединения для purchase_order_product.
  • Поскольку sales_order .completed_returned - это БУЛЕВОЙ INT, мы можем объединить его в ( 1-so.completed_returned) для преобразования total_cost 'возвращенных' элементов в безвредные нули в агрегации; GREATEST () также устраняет необходимость в критериях фильтра total_cost> 0.
  • Кроме того, если каждый purchase_order поставщик также является sales_order_supplier, то вы можете заменить LEFT OUTER JOIN на INNER JOIN для правильного вывода.
  • Наконец, как отмечалось @ RickJames , здесь также не стоит использовать DATE (), поскольку условия уже подразумевают это.

    SELECT sup.name AS имя_поставщика, sup.id AS имя_поставщика, MIN (sup.opening_balance_date) AS Открытие_баланса_Date, MIN (sup.opening_balance) AS открытие_баланса, SUM (pop.quantity * pop.cost) как total_purchases, SUM (COALESCE (GREATEST (soi.total_cost, 0) * (1-so. id = soi.sales_order_id AND so.order_status = 'complete' AND so.desired_delivery_date> = COALESCE (sup.opening_balance_date, '2020-01-01')
    WHERE sup.is_active = 1 И СУЩЕСТВУЕТ (ВЫБЕРИТЕ 1 ИЗ ПОКУПКИ ПО ЗАКАЗУ ПО ГДЕ po.created_at> = COALESCE (sup.opening_balance_date, '2020-01-01')
    И po.created_at <'2020-03-01' И po.status = 'утвержден 'AND po.supplier_id = sup.id AND pop.purchase_order_id <=> po.id) группировать по sup.id, sup.name ORDER BY sup.name;

ОБЪЯСНИТЬ ФОРМАТ = ДЕРЕВО

-> Sort: <temporary>.name
    -> Table scan on <temporary>
        -> Aggregate using temporary table
            -> Nested loop inner join  (cost=1.75 rows=1)
                -> Nested loop inner join  (cost=1.40 rows=1)
                    -> Nested loop left join  (cost=1.05 rows=1)
                        -> Nested loop left join  (cost=0.70 rows=1)
                            -> Index lookup on sup using is_active (is_active=1)  (cost=0.35 rows=1)
                            -> Index lookup on soi using reporting_supplier (supplier_id=sup.id)  (cost=0.35 rows=1)
                        -> Filter: ((so.order_status = \'complete\') and (so.desired_delivery_date >= coalesce(sup.opening_balance_date,\'2020-01-01\')))  (cost=0.35 rows=1)
                            -> Single-row index lookup on so using PRIMARY (id=soi.sales_order_id)  (cost=0.35 rows=1)
                    -> Filter: ((po.created_at >= coalesce(sup.opening_balance_date,\'2020-01-01\')) and (po.created_at < TIMESTAMP\'2020-03-01 00:00:00\') and (po. = \'approved\'))  (cost=0.35 rows=1)
                        -> Index lookup on po using supplier_id (supplier_id=sup.id)  (cost=0.35 rows=1)
                -> Index lookup on pop using purchase_order_id (purchase_order_id=po.id), with index condition: (pop.purchase_order_id <=> po.id)  (cost=0.35 rows=1)

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

Беглый взгляд на план EXPLAIN показывает, что дальнейшая оптимизация может быть возможна с использованием подсказок , то есть, в частности, оптимизаций SEMIJOIN и BNL / NO_NBL для этого запроса.

0 голосов
/ 23 марта 2020

Недостаточно информации, чтобы ответить на этот вопрос, но похоже, что предложение USE INDEX, которое вы используете во втором подзапросе, может заставить MySQL сканировать больше строк, чем необходимо. Попробуйте удалить use index (date_status_completed).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...