【问题标题】:MySQL inefficient query on Large data setMySQL对大数据集的低效查询
【发布时间】:2012-05-31 06:38:43
【问题描述】:

我们有一个如下所示的 MySQL 表(删除了无关紧要的列):

CREATE TABLE `my_data` (
  `auto_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `created_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_ts` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `data_txt` varchar(256) CHARACTER SET utf8 NOT NULL,
  `issued_ts` timestamp NULL DEFAULT NULL,
  `account_id` int(11) NOT NULL,
  PRIMARY KEY (`auto_id`),
  KEY `account_issued_idx` (`account_id`,`issued_ts`),
  KEY `account_issued_created_idx` (`account_id`,`issued_ts`,`created_ts`),
  KEY `account_created_idx` (`account_id`,`created_ts`),
  KEY `issued_idx` (`issued_ts`)
) ENGINE=InnoDB;

我们在表中有大约 900M 行,其中一个 account_id 占这些行的 65% 以上。我被要求为 created_ts 和 issue_ts 编写跨日期范围的查询,这些查询依赖于 account_id,它似乎对自动增量键具有 1:1 的功能依赖性。

典型的查询如下所示:

SELECT * 
FROM my_data 
WHERE account_id = 1 AND 
      created_ts > TIMESTAMP('2012-01-01') AND 
      created_ts <= TIMESTAMP('2012-01-21') 
ORDER BY created_ts DESC LIMIT 100;

查询的解释表明:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: my_data
         type: range
possible_keys: account_issued_idx, account_issued_created_idx, account_created_idx,
      key: account_issued_created_idx
  key_len: 8
      ref: NULL
     rows: 365314721
    Extra: Using where

问题是查询花费的时间太长并且最终被终止。我让它运行了几次,它导致数据库主机停机,因为操作系统 (Linux) 用完了交换空间。

我反复研究过这个问题,并尝试将查询分解为不相关的子查询、强制索引、使用显式 SELECT 子句并限制日期范围的窗口,但结果是一样的:差性能(太慢)和对主机的负担太重(总是死)。

我的问题是:

  1. 是否可以制定查询以将数据划分为日期范围并在实时调用中可接受地执行? (

  2. 为了获得我被要求获得的性能,我是否缺少或可能有帮助的优化?

欢迎任何其他建议、提示或想法。

谢谢

【问题讨论】:

  • 去掉Order by子句后检查性能。

标签: mysql bigdata


【解决方案1】:

这个问题已经持续多年了。不过,还是有一个很好的答案。

你奋斗的关键在于你的话删除了无关紧要的列。当你做SELECT * .... ORDER BY X DESC LIMIT N时,没有任何无关紧要的列。那是因为必须拾取和洗牌整个结果集。当您询问复杂表中的所有列时,会产生大量数据。

WHERE 子句的索引很好。如果ORDER BY 子句中没有写DESC,它也会有好处。

您想要的是延迟加入。首先只检索您需要的行的 ID。

        SELECT auto_id
          FROM my_data
         WHERE account_id = 1 AND 
              created_ts > TIMESTAMP('2012-01-01') AND 
              created_ts <= TIMESTAMP('2012-01-21') 
     ORDER BY created_ts DESC
        LIMIT 100

这将为您提供所需列的auto_id 值列表。要订购这个列表,MySql 只需要打乱 id 和 timestamp 值。需要处理的数据少了很多。

然后你 JOIN 将 ID 列表添加到你的主表并获取结果。

SELECT a.*
  FROM my_data a
  JOIN (
             SELECT auto_id
               FROM my_data
              WHERE account_id = 1 AND 
                    created_ts > TIMESTAMP('2012-01-01') AND 
                    created_ts <= TIMESTAMP('2012-01-21') 
           ORDER BY created_ts DESC
              LIMIT 100
       ) b ON a.auto_id = b.auto_id
 ORDER BY a.created_ts DESC

试试这个。它可能会为您节省很多时间。

如果您先验知道 auto_id 和 created_ts 都是单调递增的,那么您可以做得更好。您的子查询可以包含

      ORDER BY auto_id DESC
         LIMIT 100

这将减少您进一步洗牌所需的数据。

专业提示:避免在生产系统中使用SELECT *;而是枚举您实际需要的列。这有很多原因。

【讨论】:

    【解决方案2】:

    不确定为什么 MySQL 使用(显然)不是最好的索引。除了强制索引,你能不能试试EXPLAIN这个变体的计划:

    SELECT * 
    FROM my_data 
    WHERE account_id = 1 AND 
          created_ts > TIMESTAMP('2012-01-01') AND 
          created_ts <= TIMESTAMP('2012-01-21') 
    ORDER BY account_id
           , created_ts DESC 
    LIMIT 100;
    

    【讨论】:

      【解决方案3】:

      在比较中不要使用函数。计算时间戳并使用计算值,否则无法使用索引来比较created_ts,它是从结果集中过滤百万行的字段

      【讨论】:

        【解决方案4】:

        试试 MariaDB(或 MySQL 5.6),因为他们的 Optimizer 可以更快地完成。 我已经使用了几个月,对于像您这样的查询,它的速度提高了 1000%。

        您需要索引条件下推: http://kb.askmonty.org/en/index-condition-pushdown/

        【讨论】:

        • MySQL 5.6 不是稳定版本。还没有。
        • 我知道。它只是为了添加更多信息。我正在使用 MariaDB,因为该产品处于生产阶段。
        【解决方案5】:

        似乎mysql对此查询使用了错误的索引,尝试强制另一个:

        SELECT * 
        FROM my_data FORCE INDEX (`account_created_idx`)
        WHERE account_id = 1 AND 
              created_ts > TIMESTAMP('2012-01-01') AND 
              created_ts <= TIMESTAMP('2012-01-21') 
        ORDER BY created_ts DESC LIMIT 100;
        

        【讨论】:

        • 非常感谢。不知道FORCE INDEX...如果可以的话+10。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多