[英]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.