4

我们有一个看起来像这样的 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. 是否可以制定查询以将数据分割成日期范围并在实时调用中可接受地执行?( < 1s)

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

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

谢谢

4

5 回答 5

4

似乎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;
于 2012-05-24T16:41:10.627 回答
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 *在生产系统中使用;而是枚举您实际需要的列。这有很多原因。

于 2015-04-15T00:28:44.440 回答
0

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

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

于 2012-05-24T17:14:17.667 回答
0

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

于 2012-05-24T17:24:43.420 回答
0

不知道为什么 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;
于 2012-05-24T17:30:13.517 回答