带有子查询的MySQL查询花费的时间超过应有的时间

时间:2017-08-12 23:10:12

标签: mysql performance subquery database-performance

我一直试图找到查询速度减慢的原因。该查询最初是DELETE查询,但我一直在使用SELECT * from

这是有问题的查询

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
    )

在进程列表中,查询处于" SENDING DATA"的状态。但Workbench表明查询仍在运行。

这是查询的EXPLAIN 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 TABLES

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;

3 个答案:

答案 0 :(得分:1)

当您在解释中看到“DEPENDENT SUBQUERY”时,它不会缓存子查询的结果。它多次重复执行子查询(对于最外层查询中的每个不同值,一次)。我在解释中看到你的最外层查询正在检查3600万行。所以这可能会运行子查询很多次,很多次。

此处记录了这些内容:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

  

对于DEPENDENT 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只有一行,因此如果存在多个匹配,则连接将不会乘以TableA中的行数。这使它成为半连接

以下情况应该做得更好:

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。你得到的是因为你在表4中使用了OR两个不同的索引列。它使用两个索引,然后将它们联合起来。有时†最好明确使用两个子查询的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的含义,并在需要它时使用它。

†我说“有时”因为哪种方法最好可能取决于你的数据,所以你应该双向测试。

答案 1 :(得分: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;

但比尔的答案对每个人来说都应该足够了。 附:我会在Where子句中询问有关RAND()的问题,并在此处发布链接。也许它会在2025年帮助一些绝望的开发者。

答案 2 :(得分: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

  • Bill UNION似乎没必要,因为Optimizer决定使用&#34; index merge union&#34;。这可能是我第二次看到该功能正在使用中!

  • 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。这应该会显着缩小表的大小,从而加快了可缓存性和表的使用。