[英]MySQL inefficient query on Large data set
我们有一个类似于这样的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%以上。 我被要求在日期范围内为create_ts和issued_ts编写查询,这些查询依赖于account_id,而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;
对查询的EXPLAIN显示:
*************************** 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子句,并限制日期范围的窗口,但结果是相同的:性能不佳(也是对主人过于沉重(总是死亡)。
我的问题是:
是否有可能制定一个查询来将数据分割成日期范围并为可实时调用执行可接受的操作? (<1s)
我是否缺少优化或者可能有所帮助,以获得我被要求获得的性能?
欢迎任何其他建议,提示或想法。
谢谢
似乎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;
这个问题已经持续多年了。 不过,还有一个很好的答案。
你的斗争的关键在于你的话语删除了无关紧要的列。 当你做SELECT * .... ORDER BY X DESC LIMIT N
时,没有任何无关紧要的列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值。 要处理的数据很少。
然后,将该ID列表JOIN
主表并获取结果。
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 *
; 而是枚举您实际需要的列。 这有很多原因。
尝试MariaDB(或MySQL 5.6),因为他们的优化器可以更快地完成它。 我使用它几个月了,对于像你这样的一些查询,它的速度提高了1000%。
您需要索引条件下推: http : //kb.askmonty.org/en/index-condition-pushdown/
不要在比较中使用功能。 计算时间戳并使用计算值,否则你不能使用索引来比较created_ts,它是将从结果集中过滤掉数百万行的字段
不确定为什么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;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.