簡體   English   中英

優化返回行數非常大的MySQL查詢

[英]Optimizing MySQL query where the number of returned rows is very large

背景:我們有一個網站,用戶(商家)可以將他們的應用程序/網站添加到系統中,並通過 API 向用戶付款。 現在,當我們必須在他們的儀表板上向商家顯示這些交易的列表時,問題就來了。 每個商家每秒產生數百筆交易,平均每天商家有大約 200 萬筆交易,在儀表板上,我們必須向商家顯示今天的統計數據。

主要問題:我們必須向商家顯示今天的交易,單個商家大約有 200 萬條記錄。 所以像這樣的查詢,

SELECT * FROM transactions WHERE user_id = 123 LIMIT 0,15

在我們的示例中,檢查的行數為 200 萬,並且不能以任何方式減少。 我認為這個限制在這里沒有幫助,因為 MySQL 仍然會檢查所有行,然后從結果集中選擇前 15 行。

我們如何優化這樣的查詢,我們必須向用戶顯示數百萬條記錄(當然還有分頁)?

編輯:

解釋輸出:

在此處輸入圖片說明

詢問:

explain select a.id, a.user_app_id, a.created_at, a.type, a.amount, a.currency_id, b.name, b.url from transactions as a left join user_apps as b on a.user_app_id = b.id where a.sender_user_id = ? and a.created_at BETWEEN '2020-03-20' AND '2020-03-21' order by a.created_at desc limit 15 offset 0

細節:

索引sender_user_id_2sender_user_id(int)created_at(timestamp)列的復合索引。

此查詢需要 5 到 15 秒才能返回 15 行。

如果我對表中只有 24 個事務的 sender_user_id 運行相同的查詢,則立即響應。

首先,讓我們修復可能存在的錯誤:您在那個“一天”中包含了兩個午夜。 BETWEEN是“包容性”。

 AND  a.created_at BETWEEN '2020-03-20' AND '2020-03-21'

-->

 AND  a.created_at >= '2020-03-20'
 AND  a.created_at  < '2020-03-20' + INTERVAL 1 DAY

(沒有性能變化,只是消除了明天的午夜。)

在您的簡單查詢中,由於LIMIT ,只會觸及 15 行。 然而,對於更復雜的查詢,它可能需要收集所有行,對它們進行排序,然后才剝離 15 行。 防止這種低效率的技術是這樣的:如果可能,設計一個處理所有WHEREORDER BYINDEX

    where  a.sender_user_id = ?
      AND  a.created_at >= '2020-03-20'
      AND  a.created_at  < '2020-03-20' + INTERVAL 1 DAY
    order by  a.created_at desc

需要INDEX(sender_user_id, created_at) -- 按照這個順序。 (而且,在您的查詢中,沒有其他內容會侵犯它。)

通過OFFSET分頁引入了另一個性能問題——它必須在獲得您想要的行之前遍歷所有OFFSET行。 這可以通過記住你離開的地方來解決

那么,為什么EXPLAIN認為它會達到一百萬行呢? 因為 Explain 在處理LIMIT時是愚蠢的。 有一種更好的方法來估計工作量。 如果一切正常,那將顯示 15,而不是 100 萬。 對於LIMIT 150, 15 ,它將顯示 165。

您說“索引 sender_user_id_2 是 sender_user_id(int) 和 created_at(timestamp) 列的復合索引。” 你能提供SHOW CREATE TABLE以便我們可以檢查其他微妙的事情嗎?

嗯...我想知道是否

order by  a.created_at desc

應更改以匹配索引:

order by a.sender_user_id DESC, a.created_at desc

(你使用的是什么版本的 MySQL?我做了一些實驗,發現沒有區別,因為在 ORDER BY 中有(或沒有) sender_user_id 。)

(麻煩 - 似乎JOIN阻止了LIMIT的有效使用。仍在挖掘......)

新建議:

select  a.id, a.user_app_id, a.created_at, a.type, a.amount, a.currency_id,
        b.name, b.url
    from  
    (
        SELECT  a1.id
            FROM  transactions as a1
            where  a1.sender_user_id = ?
              AND  a.created_at >= '2020-03-20'
              AND  a.created_at  < '2020-03-20' + INTERVAL 1 DAY
            order by  a1.created_at desc
            limit  15 offset 0 
    ) AS x
    JOIN  transactions AS a USING(id)
    left join  user_apps as b  ON x.user_app_id = b.id 

這使用通用的“技巧”將LIMIT移動到派生表中,其他東西最少。 然后,只有 15 個 id,到其他表的JOINs變得“快速”。

在我的實驗中(使用一對不同的表),它只觸及 5*15 行。 我檢查了多個版本; 似乎所有人都需要這種技術。 我習慣用Handler_reads來驗證結果。

當我嘗試使用JOIN而不是派生表時,它觸及 2*N 行,其中 N 是沒有LIMIT的行數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM