我们有一个类似于这样的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,它似乎与自动增量键具有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子句,并限制日期范围的窗口,但结果是相同的:差性能(太慢)和对主机过于沉重(总是死亡)。
我的问题是:
是否可以制定查询以将数据分割为日期范围并为可实时调用执行可接受的操作? (&lt; 1s)
为了获得我被要求获得的表现,是否有我缺少或可能有所帮助的优化?
欢迎任何其他建议,提示或想法。
由于
答案 0 :(得分: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;
答案 1 :(得分:1)
这个问题已经持续多年了。不过,还是有一个很好的答案。
你的斗争的关键在于你的文字删除了无关紧要的列。当你SELECT * .... ORDER BY X DESC LIMIT N
时,没有任何无关紧要的列。那是因为整个结果集必须被拾取和洗牌。当你要求复杂表中的所有列时,这就是很多数据。
您有WHERE
子句的良好索引。如果ORDER BY
条款中没有DESC
,那么这对 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
条款也有好处。
您想要的是延迟加入。首先只检索所需行的ID。
auto_id
这将为您提供所需列的JOIN
值列表。要订购此列表,MySql只需要重新设置id和timestamp值。要处理的数据很少。
然后你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
主表的ID列表并获取结果。
ORDER BY auto_id DESC
LIMIT 100
试试这个。它可能会为你节省很多时间。
如果您知道先验,auto_id和created_ts都会单调增加,那么您可以做得更好。您的子查询可以包含
SELECT *
这将减少您需要进一步洗牌所需的数据。
专业提示:避免生产系统中的{{1}};而是枚举您实际需要的列。这有很多原因。
答案 2 :(得分:0)
尝试MariaDB(或MySQL 5.6),因为他们的优化工具可以更快地完成它。 我使用它几个月了,对于像你这样的一些查询,它的速度提高了1000%。
您需要索引条件下推: http://kb.askmonty.org/en/index-condition-pushdown/
答案 3 :(得分:0)
不要在比较中使用功能。计算时间戳并使用计算值,否则你不能使用索引来比较created_ts,它是将从结果集中过滤掉数百万行的字段
答案 4 :(得分: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;