【问题标题】:ORDER BY datetime makes the query very slowORDER BY datetime 使查询非常慢
【发布时间】:2013-03-20 00:02:14
【问题描述】:

我正在尝试从多个表中提取数据,当我使用 ORDER BY 日期时间字段时,它会在至少 10 秒后返回结果,但如果我在没有 ORDER BY 的情况下执行相同的查询,那么它会在 2 秒内返回结果。

这是我当前的查询

SELECT
ph.call_subject AS callSubject,
ac.account_name AS accountName,
DATE_FORMAT(ph.trigger_on, "%c/%e/%Y %h:%i %p") AS triggerOn,
ind.name AS industry,
cc.call_code_name AS callCode
FROM phone_calls AS ph
INNER JOIN accounts AS ac ON ph.account_id = ac.account_id
INNER JOIN industries AS ind ON ind.industry_id = ac.industry_id
INNER JOIN call_codes AS cc ON ph.call_code_id = cc.call_code_id
WHERE ac.status = 1 AND ph.status = 1 AND ph.owner_id = 1 AND ac.do_not_call = 0
AND ph.trigger_on BETWEEN '2012-11-19 00:00:00' AND '2013-03-19 23:59:59'
ORDER BY ph.trigger_on ASC LIMIT 0,1000

以下字段均为 INT(11) UNSIGNED 类型

ph.account_id
ac.account_id
ind.industry_id
ac.industry_id
ph.call_code_id
cc.call_code_id
ph.owner_id

以下字段均为 tinyint(1) 类型

ac.status 
ph.status
ac.do_not_call

此字段为日期时间类型

ph.trigger_on

请注意,accounts 有 300K 条记录,phone_calls 有 500 万条记录。 我可以做些什么来更快地执行这个 ORDER BY?请注意,我的所有 where 子句字段、我的所有 ON 子句和 ph.trigger_on 都已编入索引。我使用的是 InnoDB 存储引擎而不是 MyIsam。

谢谢

【问题讨论】:

  • 请包含表定义,以便我们查看您选择的类型和索引位置
  • 请再次查看我的帖子,因为我更新了一些字段类型
  • 你能告诉我们EXPLAIN返回什么吗?

标签: mysql sql-order-by


【解决方案1】:

请试试这个:

  1. (phone_calls.trigger_on, phone_calls.status, phone_calls.owner_id) 列上建立索引,称之为pcto

  2. 将您的 FROM 子句更改为:

    FROM phone_calls AS ph FORCE INDEX (pcto)

这是最理想的。如果它不起作用,请添加评论,我将为您提供另一种保证有效的方法并为您提供所需的性能改进。

请注意:在查询中的“每个”列上建立索引并不重要(而且确实没有好处)。 MySQL 每个表只能使用一个索引(或者更准确地说,每个表别名)。您需要构建我们告诉您的索引。

【讨论】:

  • 好的,哇,成功了!您能否告诉我您作为选项 B 的第二个想法是什么,这样当我在不同的场景中再次遇到此问题时,我可以使用另一个技巧?谢谢你。
  • 另一种方法,只有在真正需要时才使用(而且非常罕见,它永远需要)是用子选择替换 FROM 子句,因此“FROM (SELECT * FROM phone_calls phs WHERE phs.status = 1 AND phs.owner_id = 1 ORDER BY phs.trigger_on) AS ph"。您仍然需要按照我的回答构建正确的索引。顺便说一句,现在您已经建立了正确的索引,请尝试不使用“FORCE INDEX pcto”的查询。您可能会发现它仍然可以正常工作。仅有索引是不够的,你需要有合理的索引。
  • 非常感谢 Ben 给了你很多帮助 :)
【解决方案2】:

如果您的 LIMIT 为 5 行,那么在没有顺序的情况下,查询可以抓取它找到的与您的其他条件匹配的前 5 行。

如果您有 ORDER BY 子句,它必须查看与您的其他条件匹配的所有行并选择最低的 5 个。

【讨论】:

  • 我刚刚更新了我的帖子并更改了限制。出于测试目的,我限制了 5,但它会比 5 拉得更多。那么我怎样才能在不失去这个速度因素的情况下对结果进行排序呢?
  • 基本上你不能。 ORDER 所花费的时间是运行完整查询的实际时间。没有 ORDER 的 LIMIT 只是在掩饰这一点。如果您的应用程序中有可用内存,您可能会发现在没有 ORDER 的情况下查询所有行并在应用程序中排序会更快。在临时表dev.mysql.com/doc/refman/5.1/en/internal-temporary-tables.html 上查看此文档。如果 MySQL 正在磁盘上为您的查询构建一个临时表,那么将所有内容放入应用程序的内存中然后对其进行排序可能会更快。
【解决方案3】:

根据我的经验,从 SQL 查询中获得性能的最快方法是将其简化为多个步骤。利用临时表并减少每一步的连接和操作数量(消耗内存,获得速度)。请原谅下面可能出现的语法错误,因为我已经很长时间没有使用 MySQL,但您可以按如下方式重写您的查询:

CREATE TEMPORARY TABLE scratch1 AS (
    SELECT
            ph.call_subject AS callSubject,
            ac.account_name AS accountName,
            DATE_FORMAT(ph.trigger_on, "%c/%e/%Y %h:%i %p") AS triggerOn,
            ac.industry_id,
            ph.call_code_id
    FROM
            phone_calls AS ph
            INNER JOIN accounts AS ac ON ph.account_id = ac.account_id
    WHERE   
            ac.status = 1 AND ph.status = 1 AND ph.owner_id = 1 AND ac.do_not_call = 0
            AND ph.trigger_on BETWEEN '2012-11-19 00:00:00' AND '2013-03-19 23:59:59' )

ALTER TABLE scratch1 ADD industry VARCHAR(255)
ALTER TABLE scratch1 ADD callCode VARCHAR(255)

UPDATE scratch1 s JOIN industries ind ON ind.industry_id = s.industry_id
SET s.industry = ind.name

UPDATE scratch1 s JOIN call_codes cc ON cc.call_code_id = s.call_code_id
SET s.callCode = cc.call_code_name

CREATE TEMPORARY TABLE scratch2 AS (
    SELECT * FROM scratch1 ORDER BY triggerOn ASC )

SELECT * FROM scratch2 LIMIT 0, 1000

【讨论】:

  • 我不能像这样使用临时表。对于系统使用每天执行 1000 次查询的过程来说,这是一个很好的解决方案。我从不使用使用临时表进行常规查询的网络应用程序。
  • 虽然在这种情况下我同意提议的临时表没有帮助,但建议它们仅对程序有用并且仅仅因为您“从不使用 [SIC]使用临时表进行常规查询”,并不意味着它们在某些情况下并不完全合理。请不要来向有经验的程序员寻求建议,然后声称比他们了解更多。如果你知道这么多,就不用在这里问了吧!
  • @CaptainPayalytic,我没说我比你知道的多!我只是说我从未见过每天执行 1000 次的查询会发生这种情况。但是你在其他事情上使用你的想法很多,比如报告和临时项目。感谢您的帮助:)
【解决方案4】:

这是为了详细说明Ersun的解决方案/评论。

没有order by,SQL 将评估查询。在这种情况下,它是一堆连接。很可能,您在连接字段上有索引。因此,查询通过从phone_calls 读取记录、查找数据、检查过滤条件并返回它来进行。然后它进入记录等等。总的来说,它可能会读取几千或几万条记录。

使用order by,SQL 必须评估查询中的所有记录。它必须读取所有电话,因为最后一个电话可能具有最小值。然后它会进行排序并返回正确的记录。

您可以通过在phone_calls(status, owner_id, trigger_on) 上建立索引来满足where 子句来加快查询速度。

【讨论】:

  • 你的意思是有一个3列的二进制表吗?请注意,owner_id 值将根据登录网站的用户 ID 更改。我的值为 1,因为这是我的个人 ID。
  • @Mike 。 . .我的意思是你想要一个多列索引。
  • 但我已经将它们编入索引。我将列中使用的每一列都编入索引。
【解决方案5】:

当您在 (SELECT) aka 上执行 SELECT 时,这真的就像处理临时表一样。下面的示例在一个主大表上有几个连接。此解决方案将查询时间缩短到 0.2 秒,而当 ORDER BY 针对整个表查询时为 20 秒。

   SELECT * FROM (SELECT `cse_notes`.`notes_id`, `cse_notes`.`dateandtime`, 
    `cse_case`.`case_id`, `cse_case_notes`.`attribute`
    FROM  `cse_notes` 
    INNER JOIN  `cse_case_notes` 
    ON `cse_notes`.`notes_uuid` =  `cse_case_notes`.`notes_uuid`
    INNER JOIN `cse_case` 
    ON  `cse_case_notes`.`case_uuid` = `cse_case`.`case_uuid`
    WHERE `cse_notes`.`deleted` = 'N' AND `cse_case`.`case_id` = :case_id
    AND `cse_notes`.customer_id = :customer_id) notes
    ORDER BY `dateandtime` DESC

这是运行非常缓慢的错误查询。我认为这很好,我不知道必须在过滤开始之前对整个表进行排序。单独建立索引没有帮助。

    SELECT `cse_notes`.`notes_id`, `cse_notes`.`dateandtime`,
    `cse_case`.`case_id`, `cse_case_notes`.`attribute`    
    FROM  `cse_notes`     
    INNER JOIN  `cse_case_notes` ON `cse_notes`.`notes_uuid` =  `cse_case_notes`.`notes_uuid`    
    INNER JOIN `cse_case` ON  `cse_case_notes`.`case_uuid` = `cse_case`.`case_uuid`    
    WHERE `cse_notes`.`deleted` = 'N' 
    AND `cse_case`.`case_id` = :case_id    
    AND `cse_notes`.customer_id = :customer_id    
    ORDER BY `cse_notes`.dateandtime DESC LIMIT 0, 1000

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多