У меня есть таблица с почти 7 миллионами строк. Вот структура таблицы

 `CREATE TABLE `ERS_SALES_TRANSACTIONS` (
  `saleId` int(12) NOT NULL AUTO_INCREMENT,
  `ERS_COMPANY_CODE` int(3) DEFAULT NULL,
  `SALE_SECTION` varchar(128) DEFAULT NULL,
  `SALE_DATE` date DEFAULT NULL,
  `SALE_STOCKAGE_EXACT` int(4) DEFAULT NULL,
  `SALE_NET_AMOUNT` decimal(11,2) DEFAULT NULL, 
  `SALE_ABSOLUTE_CDATE` date DEFAULT NULL,
  PRIMARY KEY (`saleId`),
  KEY `index_location` (`ERS_COMPANY_CODE`),
  KEY `idx-erscode-salesec` (`SALE_SECTION`,`ERS_COMPANY_CODE`) USING BTREE,
  KEY `idx-saledate-section` (`SALE_DATE`,`SALE_SECTION`) USING BTREE
  KEY `idx_quick_sales_transactions` (`ERS_COMPANY_CODE`,`SALE_SECTION`,`SALE_DATE`,`SALE_STOCKAGE_EXACT`,`SALE_NET_AMOUNT`)
) ENGINE=InnoDB;

На выполнение этого запроса уходит более 7 секунд, есть ли способ ускорить это?

SELECT 
   A.SALE_SECTION,  
   SUM(IF(A.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 0 AND 90, A.SALE_NET_AMOUNT, 0)) AS fs1_pd1_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 91 AND 180, A.SALE_NET_AMOUNT, 0)) AS fs2_pd1_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 181 AND 365, A.SALE_NET_AMOUNT, 0)) AS os1_pd1_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 366 AND 9999, A.SALE_NET_AMOUNT, 0)) AS os2_pd1_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30', A.SALE_NET_AMOUNT, 0)) AS TOTAL_PD1_SALE,
   SUM(IF(A.SALE_DATE BETWEEN '2016-04-01' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 0 AND 90, A.SALE_NET_AMOUNT, 0)) AS fs1_pd2_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-04-01' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 91 AND 180, A.SALE_NET_AMOUNT, 0)) AS fs2_pd2_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-04-01' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 181 AND 365, A.SALE_NET_AMOUNT, 0)) AS os1_pd2_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-04-01' AND '2016-04-30'
          AND A.SALE_STOCKAGE_EXACT BETWEEN 366 AND 9999, A.SALE_NET_AMOUNT, 0)) AS os2_pd2_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-04-01' AND '2016-04-30', A.SALE_NET_AMOUNT, 0)) AS TOTAL_PD2_SALE,
   SUM(IF(A.SALE_DATE BETWEEN '2016-05-01' AND '2016-05-31'
          AND A.SALE_ABSOLUTE_CDATE BETWEEN '2016-03-01' AND '2016-05-31', A.SALE_NET_AMOUNT, 0)) AS fs1_achived_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-05-01' AND '2016-05-31'
          AND A.SALE_ABSOLUTE_CDATE BETWEEN '2015-12-01' AND '2016-02-29', A.SALE_NET_AMOUNT, 0)) AS fs2_achived_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-05-01' AND '2016-05-31'
          AND A.SALE_ABSOLUTE_CDATE BETWEEN '2015-06-01' AND '2015-11-30', A.SALE_NET_AMOUNT, 0)) AS os1_achived_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-05-01' AND '2016-05-31'
          AND A.SALE_ABSOLUTE_CDATE BETWEEN '2006-12-26' AND '2015-05-31', A.SALE_NET_AMOUNT, 0)) AS os2_achived_sale,
   SUM(IF(A.SALE_DATE BETWEEN '2016-05-01' AND '2016-05-31', A.SALE_NET_AMOUNT, 0)) AS Total_ACHIVED_SALE
   FROM ERS_SALES_TRANSACTIONS A WHERE A.ERS_COMPANY_CODE = 48 GROUP BY A.SALE_SECTION

Вот объясните запрос

{
"data":
[
    {
        "id": 1,
        "select_type": "SIMPLE",
        "table": "A",
        "type": "ref",
        "possible_keys": "index_location,idx-erscode-salesec,idx-saledate-section",
        "key": "index_location",
        "key_len": "5",
        "ref": "const",
        "rows": 1411944,
        "Extra": "Using where; Using temporary; Using filesort"
    }
]
}

После добавления составного индекса время уменьшилось до 4,03 сек. Вот план

{
"data":
[
    {
        "id": 1,
        "select_type": "SIMPLE",
        "table": "A",
        "type": "ref",
        "possible_keys": "index_location,idx-erscode-salesec,idx-saledate-section,idx_quick_sales_transactions",
        "key_len": "5",
        "key": "idx_quick_sales_transactions",
        "ref": "const",
        "rows": 1306058,
        "Extra": "Using where"
    }
]

}

0
sam 10 Май 2016 в 13:07

3 ответа

Лучший ответ

Я не согласен с Джимми Б. На мой взгляд, ваш запрос выглядит идеально.

В зависимости от того, сколько записей существует для компании 48, следует либо последовательно читать всю таблицу (когда ее много, скажем, 50% всех записей таблицы), либо следует использовать индекс по ERS_COMPANY_CODE (когда их не так много, скажем, только 1% от всех записей).

Поскольку СУБД решила использовать индекс для ERS_COMPANY_CODE, последний вариант должен иметь место.

Вы можете попытаться еще больше ускорить запрос, создав составной индекс. Сделайте это хотя бы (ERS_COMPANY_CODE , SALE_SECTION), чтобы GROUP BY было быстрее. Лучше даже добавить все поля, чтобы все данные можно было собрать из индекса, и к самой таблице больше не нужно было обращаться.

CREATE INDEX idx_quick_sales_transactions ON ERS_SALES_TRANSACTIONS
  (ERS_COMPANY_CODE, SALE_SECTION, SALE_DATE, SALE_STOCKAGE_EXACT, SALE_NET_AMOUNT);
1
Thorsten Kettner 10 Май 2016 в 10:25

Я не знаю, есть ли способ ускорить это. Но вы можете попробовать использовать index. Я бы порекомендовал один на ERS_SALES_TRANSACTIONS(ERS_COMPANY_CODE, SALE_SECTION, SALE_DATE, SALE_NET_AMOUNT).

Это покрывающий индекс для запроса, означающий, что все столбцы, используемые для запроса, находятся в индексе - и, следовательно, механизму базы данных не требуется доступ к исходным страницам данных.

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

2
Gordon Linoff 10 Май 2016 в 10:26
SELECT 
   sales.SALE_SECTION,
   SUM( fs1_pd1.SALE_NET_AMOUNT ) AS fs1_pd1_sale,
   SUM( fs2_pd1.SALE_NET_AMOUNT ) AS fs2_pd1_sale,
...
FROM ERS_SALES_TRANSACTIONS sales

LEFT OUTER JOIN ERS_SALES_TRANSACTIONS fs1_pd1 ON sales.ERS_COMPANY_CODE = fs1_pd1.ERS_COMPANY_CODE AND sales.SALE_SECTION = fs1_pd1.SALE_SECTION
  AND fs1_pd1.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30'
  AND fs1_pd1.SALE_STOCKAGE_EXACT BETWEEN 0 AND 90

LEFT OUTER JOIN ERS_SALES_TRANSACTIONS fs2_pd1 ON sales.ERS_COMPANY_CODE = fs2_pd1.ERS_COMPANY_CODE AND sales.SALE_SECTION = fs2_pd1.SALE_SECTION
  AND fs2_pd1.SALE_DATE BETWEEN '2016-01-16' AND '2016-04-30'
  AND fs2_pd1.SALE_STOCKAGE_EXACT BETWEEN 91 AND 180
...
   WHERE sales.ERS_COMPANY_CODE = 48
   GROUP BY sales.SALE_SECTION

Таким образом, оптимизатор может использовать более одного индекса для запроса.

Однако я предлагаю сначала попробовать составной индекс, который рекомендует @Thorsten Kettner, поскольку он может дать тот же эффект с гораздо меньшей сложностью.

0
JimmyB 10 Май 2016 в 10:31