2017-08-12 36 views
1

我一直在試圖找出查詢中減速的原因。查詢原本是DELETE查詢,但我一直在使用一個SELECT * FROM使用子查詢的MySQL查詢花費的時間比應該長

這是有問題的查詢

SELECT * FROM table1 
where table1.id IN (
#Per friends suggestion I wrapped the subquery in a subquery (yo dawg) to "cache" it, it works on other queries, but not on this time. 
SELECT id FROM (  
(
    SELECT id FROM (
     SELECT table1.id FROM table1 
     LEFT JOIN table2 ON table2.id = table1.salesperson_id 
     LEFT JOIN table3 ON table3.id = table2.user_id 
     LEFT JOIN table4 ON table3.office_id = table4.id 
     WHERE table1.type = "Snapshot" 
     AND table4.id = 25 OR table4.parent_id =25 
     LIMIT 500 
    ) AS ids) 
) AS moreIds 
) 

問題的表是16場演出。
它正在運行的服務器足夠強壯不會成爲瓶頸。 字段id,salesperson_id和類型都被索引。檢查它5次。

子查詢本身運行速度非常快。子查詢:

SELECT id FROM (
     SELECT table1.id FROM table1 
     LEFT JOIN table2 ON table2.id = table1.salesperson_id 
     LEFT JOIN table3 ON table3.id = table2.user_id 
     LEFT JOIN table4 ON table3.office_id = table4.id 
     WHERE table1.type = "Snapshot" 
     AND table4.id = 25 OR table4.parent_id =25 
     LIMIT 500 
    ) 

在進程列表中,查詢處於「發送數據」狀態。但工作臺表明查詢仍在運行。

這裏有一個解釋查詢的SELECT

'1', 'PRIMARY', 'table1', 'index', NULL, 'SALES_FK_ON_SALES_STATE', '5', NULL, '36688459', 'Using where; Using index' 
'2', 'DEPENDENT SUBQUERY', '<derived3>', 'ALL', NULL, NULL, NULL, NULL, '500', 'Using where' 
'3', 'DERIVED', '<derived4>', 'ALL', NULL, NULL, NULL, NULL, '500', '' 
'4', 'DERIVED', 'table4', 'index_merge', 'PRIMARY,IDX_9F61CEFC727ACA70', 'PRIMARY,IDX_9F61CEFC727ACA70', '4,5', NULL, '67', 'Using union(PRIMARY,IDX_9F61CEFC727ACA70); Using where; Using index' 
'4', 'DERIVED', 'table3', 'ref', 'PRIMARY,IDX_C077730FFFA0C224', 'IDX_C077730FFFA0C224', '5', 'hugeDb.table4.id', '381', 'Using where; Using index' 
'4', 'DERIVED', 'table2', 'ref', 'PRIMARY,UNIQ_36E3BDB1A76ED395', 'UNIQ_36E3BDB1A76ED395', '5', 'hugeDb.table3.id', '1', 'Using where; Using index' 
'4', 'DERIVED', 'table1', 'ref', 'SALESPERSON,SALES_FK_ON_SALES_STATE', 'SALES_FK_ON_SALES_STATE', '5', 'hugeDb.table2.id', '115', 'Using where' 

這裏有SHOW創建表

CREATE TABLE `table4` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `logo_file_id` int(11) DEFAULT NULL, 
    `contact_address_id` int(11) DEFAULT NULL, 
    `billing_address_id` int(11) DEFAULT NULL, 
    `parent_id` int(11) DEFAULT NULL, 
    `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `fax` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `contact_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `active` tinyint(1) NOT NULL, 
    `date_modified` datetime DEFAULT NULL, 
    `date_created` datetime NOT NULL, 
    `license_number` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `list_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `routing_address_id` int(11) DEFAULT NULL, 
    `billed_separately` tinyint(1) DEFAULT '0', 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `UNIQ_9F61CEFCA7E1931C` (`logo_file_id`), 
    KEY `IDX_9F61CEFC320EF6E2` (`contact_address_id`), 
    KEY `IDX_9F61CEFC79D0C0E4` (`billing_address_id`), 
    KEY `IDX_9F61CEFC727ACA70` (`parent_id`), 
    KEY `IDX_9F61CEFC40F0487C` (`routing_address_id`), 
    -- CONSTRAINT `FK_9F61CEFC320EF6E2` FOREIGN KEY (`contact_address_id`) REFERENCES `other_irrelevant_table` (`id`), 
    -- CONSTRAINT `FK_9F61CEFC79D0C0E4` FOREIGN KEY (`billing_address_id`) REFERENCES `other_irrelevant_table` (`id`), 
    -- CONSTRAINT `FK_9F61CEFCA7E1931C` FOREIGN KEY (`logo_file_id`) REFERENCES `other_irrelevant_table` (`id`), 
    -- CONSTRAINT `FK_9F61CEFCE346079F` FOREIGN KEY (`routing_address_id`) REFERENCES `other_irrelevant_table` (`id`), 
    CONSTRAINT `FK_9F61CEFC727ACA70` FOREIGN KEY (`parent_id`) REFERENCES `table4` (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=750 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

CREATE TABLE `table3` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `office_id` int(11) DEFAULT NULL, 
    `user_id` int(11) DEFAULT NULL, 
    `active` tinyint(1) NOT NULL, 
    `date_modified` datetime DEFAULT NULL, 
    `date_created` datetime NOT NULL, 
    `profile_id` int(11) DEFAULT NULL, 
    `deleted` tinyint(1) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `IDX_C077730FFFA0C224` (`office_id`), 
    KEY `IDX_C077730FA76ED395` (`user_id`), 
    KEY `IDX_C077730FCCFA12B8` (`profile_id`), 
    -- CONSTRAINT `FK_C077730FA76ED395` FOREIGN KEY (`user_id`) REFERENCES `other_irrelevant_table` (`id`), 
    -- CONSTRAINT `FK_C077730FCCFA12B8` FOREIGN KEY (`profile_id`) REFERENCES `other_irrelevant_table` (`id`), 
    CONSTRAINT `FK_C077730FFFA0C224` FOREIGN KEY (`office_id`) REFERENCES `table4` (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=382425 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

CREATE TABLE `table2` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `user_id` int(11) DEFAULT NULL, 
    `active` tinyint(1) NOT NULL, 
    `date_modified` datetime DEFAULT NULL, 
    `date_created` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `UNIQ_36E3BDB1A76ED395` (`user_id`), 
    CONSTRAINT `FK_36E3BDB1A76ED395` FOREIGN KEY (`user_id`) REFERENCES `table3` (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=174049 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

CREATE TABLE `table1` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `salesperson_id` int(11) DEFAULT NULL, 
    `count_active_contracts` int(11) NOT NULL, 
    `average_initial_price` decimal(12,2) NOT NULL, 
    `average_contract_value` decimal(12,2) NOT NULL, 
    `total_sold` int(11) NOT NULL, 
    `total_active` int(11) NOT NULL, 
    `active` tinyint(1) NOT NULL, 
    `date_modified` datetime DEFAULT NULL, 
    `date_created` datetime NOT NULL, 
    `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, 
    `services_scheduled_today` int(11) NOT NULL, 
    `services_scheduled_week` int(11) NOT NULL, 
    `services_scheduled_month` int(11) NOT NULL, 
    `services_scheduled_summer` int(11) NOT NULL, 
    `serviced_today` int(11) NOT NULL, 
    `serviced_this_week` int(11) NOT NULL, 
    `serviced_this_month` int(11) NOT NULL, 
    `serviced_this_summer` int(11) NOT NULL, 
    `autopay_account_percentage` decimal(3,2) NOT NULL, 
    `value_per_door` decimal(12,2) NOT NULL, 
    `total_paid` int(11) NOT NULL, 
    `sales_status_summary` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, 
    `total_serviced` int(11) NOT NULL, 
    `services_scheduled_year` int(11) NOT NULL, 
    `serviced_this_year` int(11) NOT NULL, 
    `services_scheduled_yesterday` int(11) NOT NULL, 
    `serviced_yesterday` int(11) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `SALESPERSON` (`type`), 
    KEY `SALES_FK_ON_SALES_STATE` (`salesperson_id`), 
    CONSTRAINT `SALES_FK_ON_SALES_STATE` FOREIGN KEY (`salesperson_id`) REFERENCES `table2` (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=181662521 DEFAULT CHARSET=utf8; 
+0

感謝您使用EXPLAIN。另外,在詢問SQL優化問題時,還應該爲查詢中引用的每個表包含「SHOW CREATE TABLE」的輸出,以便我們可以知道迄今爲止創建的任何索引,以及數據類型和約束。幫助我們幫助你! –

+0

TIL。謝謝比爾。但我該如何解決這個問題?我會在一瞬間包含創建語句 –

+0

添加了SHOW CREATE TABLES。 –

回答

1

當你在解釋見「養SUBQUERY」,它是不緩存的結果子查詢。它多次重新執行子查詢(最外層查詢中的每個不同值都執行一次)。我在解釋中看到,最外層的查詢正在檢查3600萬行。所以這可能運行子查詢很多,很多次。

這被記錄在這裏:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

供養SUBQUERY,子查詢重新只計算一次針對每組從其外上下文變量的不同的值。對於UNCACHEABLE SUBQUERY,子查詢會針對外部上下文的每一行重新評估。爲了避免這種

的一種方式是使用一個子查詢作爲派生表代替作爲參數的IN()謂詞。這是一種更好的方式來進行像您一樣的半連接。

SELECT ... FROM TableA 
WHERE TableA.id IN (SELECT id FROM ...) 

應該等於:

SELECT ... FROM TableA 
JOIN (SELECT DISTINCT id FROM ...) AS TableB 
    ON TableA.id = TableB.id 

在子查詢中使用DISTINCT意味着有每個子查詢返回的ID只有一行,所以加入將不會從乘行數表A如果有多個匹配。這使得它成爲半連接

下應該做的更好:

SELECT table1.* 
FROM table1 
JOIN (
    SELECT table1.id FROM table1 
    LEFT JOIN table2 ON table2.id = table1.salesperson_id 
    LEFT JOIN table3 ON table3.id = table2.user_id 
    LEFT JOIN table4 ON table3.office_id = table4.id 
    WHERE table1.type = 'Snapshot' 
    AND table4.id = 25 OR table4.parent_id =25 
    LIMIT 500 
) AS ids ON table1.id = ids.id; 

您也可以嘗試擺脫index_merge的。你得到的是因爲你使用OR爲表4中的兩個不同的索引列。它使用兩個索引,然後將它們聯合起來。有時候†最好明確地使用兩個子查詢的UNION,而不是依賴index_merge。

SELECT table1.* 
FROM table1 
JOIN (
    SELECT table1.id FROM table1 
    JOIN table2 ON table2.id = table1.salesperson_id 
    JOIN table3 ON table3.id = table2.user_id 
    JOIN (
     SELECT id FROM table4 WHERE id=25 
     UNION 
     SELECT id FROM table4 WHERE parent_id=25 
    ) AS t4 ON table3.office_id = t4.id 
    WHERE table1.type = 'Snapshot' 
    LIMIT 500 
) AS ids ON table1.id = ids.id; 

您也在不必要地使用了LEFT JOIN,所以我用JOIN替換了它。 MySQL優化器會默默地將其轉換爲內部連接,但我認爲您應該研究LEFT JOIN的含義,並在需要時使用它。

†我說「有時」,因爲哪種方法最好可能取決於您的數據,所以您應該測試它的兩種方式。

+0

哇,哇。謝謝比爾!這個答案比我希望的要多得多。我無法表達我對你的感謝!您的答案通過超過幾個月的工作提升了我的MySQL技能。另外,我發現了另一個解決我的問題的方法。我將在幾分鐘內發佈它 –

0

由於我需要限制刪除查詢與連接(這是不可能在MySQL中),還有一個選項。這絕不是最好的選擇(無法擊敗比爾的答案)。

但它工作,查詢速度非常快,儘管不是非常靈活。由於它具有行的最低金額也可以拉,這對於一個38M行表是575K(不知道爲什麼)

但在這裏,它是:

SELECT COUNT(*) FROM table1 
    JOIN table2 ON table2.id = table1.salesperson_id 
    JOIN table3 ON table3.id = table2.user_id 
    JOIN table4 ON table3.office_id = table4.id 
WHERE table1.type = "Snapshot" 
    AND table4.id = 113 OR table4.parent_id =113 
    AND RAND()<=0.001; 

但比爾的答案應該是綽綽有餘大家。 P.S.我會在Where Where子句中詢問有關RAND()的問題,並在此處發佈鏈接。也許這將有助於在2025年

+0

FWIW,您*可以*在MySQL中使用多表DELETE語法。請參閱https://dev.mysql.com/doc/refman/5.7/en/delete.html –

+0

我不能限制它,這就是問題所在 –

0

有些絕望開發你被衝昏頭腦嵌套等

SELECT table1.* 
    FROM 
    (
     SELECT table1.id 
      FROM table1 
      JOIN table2 ON table2.id = table1.salesperson_id 
      JOIN table3 ON table3.id = table2.user_id 
      JOIN table4 ON table3.office_id = table4.id 
      WHERE table1.type = "Snapshot" 
       AND table4.id = 25 
       OR table4.parent_id =25 
      LIMIT 500 
    ) AS ids 
    JOIN table1 USING(id) 

一些討論:

  • 這是更好地找到500個id和投他們進入tmp表格,而不是在table1.*的所有列周圍拖拽。因此子查詢爲LIMIT 500

  • 比爾的UNION由於Optimizer決定使用「索引合併聯合」似乎是不必要的。這可能只是我第二次看到該功能在使用!

  • IN (SELECT ...)可能永遠不會比等效的JOINEXISTS更快,以較合適的爲準。 (JOIN是適合您的情況。)

  • 對於table4,你有logo_file_id一個完美的「自然PK,爲什麼不擺脫id和促進去PK? (類似於table2。)

  • Aarrgghh ...按照我以前的建議,您可以繞過table2

  • table1有181M行? INT總是4個字節。你有很多像專櫃一樣的專欄;考慮使用TINYINT UNSIGNED(1字節;範圍:0..255)或SMALLINT UNSIGNED。這應該會顯着縮小表的大小,從而加快緩存和表的使用。