簡體   English   中英

在具有 10 多萬行的表上使用 1 個連接優化查詢

[英]Optimize query with 1 join, on tables with 10+ millions rows

我正在考慮使用 2 個表更快地提出請求。
我有以下 2 個表:

表“日志”

  • id varchar(36) PK
  • date timestamp(2)
  • 更多 varchar 字段和一個文本字段

該表具有 PHP Laravel 框架所稱的與其他幾個對象的“多態多對多”關系,因此還有第二個表“logs_pivot”:

  • id unsigned int PK
  • log_id varchar(36) FOREIGN KEY (logs.id)
  • model_id varchar(40)
  • model_type varchar(50)

有一個或幾個條目logs_pivot每個條目logs 它們分別有 20+ 和 10+ 百萬行。

我們做這樣的查詢:

select * from logs 
join logs_pivot on logs.id = logs_pivot.log_id
where model_id = 'some_id' and model_type = 'My\Class'
order by date desc
limit 50;

顯然,我們在 model_id 和 model_type 字段上都有一個復合索引,但請求仍然很慢:每次都有幾(幾十)秒。
我們在date字段上也有一個索引,但EXPLAIN顯示這是使用的model_id_model_type索引。

解釋聲明:

+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
| id | select_type | table       | partitions | type   | possible_keys                                                                  | key                                           | key_len | ref                                       | rows | filtered | Extra                           |
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
|  1 | SIMPLE      | logs_pivot  | NULL       | ref    | logs_pivot_model_id_model_type_index,logs_pivot_log_id_index | logs_pivot_model_id_model_type_index | 364     | const,const                               |    1 |   100.00 | Using temporary; Using filesort |
|  1 | SIMPLE      | logs        | NULL       | eq_ref | PRIMARY                                                                        | PRIMARY                                       | 146     | the_db_name.logs_pivot.log_id |    1 |   100.00 | NULL                            |
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+

在其他表中,通過在索引中包含日期字段,我能夠更快地發出類似的請求。 但在這種情況下,它們位於單獨的表中。

當我們想要訪問這些數據時,它們通常是幾個小時/幾天前的。
我們的 InnoDB 池太小,無法在內存中保存所有數據(+所有其他表),因此數據很可能總是在磁盤上查詢。

我們可以通過哪些方式更快地提出該請求?
理想情況下,只使用另一個索引,或者通過改變它的完成方式。

非常感謝 !


編輯 17h05 :
到目前為止,謝謝大家的回答,我會嘗試像 O Jones 建議的那樣,並以某種方式將日期字段包含在數據透視表中,以便我可以包含在索引索引中。


編輯 14/10 10 小時。

解決方案 :

所以我最終改變了請求的真正完成方式,通過對數據透視表的 id 字段進行排序,這確實允許將其放入索引中。

此外,當未按日期過濾時,計算總行數的請求更改為僅在數據透視表上完成。

謝謝你們 !

我看到兩個問題:

  • 當表相對於 RAM 大小而言很大時,UUID 成本很高。

  • 由於WHERE子句來自一個表,而ORDER BY列來自另一個表,因此無法以最佳方式處理LIMIT 也就是說,它將執行所有JOIN ,然后排序並最后剝離幾行。

只是一個建議。 使用復合索引顯然是一件好事。 另一種可能是按日期對 ID 進行資格預審,並根據 (model_id, model_type, log_id ) 上的 logs_pivot 表索引擴展您的索引。

如果您的查詢數據和整個歷史記錄是 20 多萬條記錄,那么數據可以追溯到多遠,您只需要處理每個給定的模型 ID/類型類別的 50 條記錄的限制。 說3個月? vs 說你 5 年的日志? (沒有在帖子中列出,只是一個例子)。 因此,如果您可以查詢日期大於 3 個月前的最小日志 ID,則該 ID 可以限制您的 logs_pivot 表中發生的其他事情。

就像是

select
      lp.*,
      l.date
   from
      logs_pivot lp
         JOIN Logs l
            on lp.log_id = l.id
   where
          model_id = 'some_id' 
      and model_type = 'My\Class'
      and log_id >= ( select min( id )
                         from logs
                        where date >= datesub( curdate(), interval 3 month ))
   order by 
      l.date desc
   limit  
      50;

因此,log_id 的 where 子句只執行一次,並且只返回 3 個月前的 ID,而不是 logs_pivot 的整個歷史記錄。 然后使用優化后的model id/type 兩部分鍵進行查詢,同時也使用索引鍵中包含的ID 跳轉到其索引的末尾以跳過所有歷史記錄。

您可能想要包含的另一件事是一些預聚合表,其中包含每個給定模型類型/ID 的記錄數量,例如每月/每年。 將其用作向用戶呈現的預查詢,然后您可以將其用作深入了解以進一步獲取更多詳細信息。 預聚合表可以對所有歷史內容進行一次,因為它是靜態的並且不會改變。 您唯一需要不斷更新的是當前單月期間的內容,例如每晚更新一次。 或者甚至可能更好,通過觸發器在每次添加完成時插入記錄,或者根據年/月聚合更新給定模型/類型的計數。 同樣,只是一個建議,因為沒有關於如何/為什么將數據呈現給最終用戶的其他上下文。

SELECT columns FROM big table ORDER BY something LIMIT small number是一個臭名昭著的查詢性能反模式。 為什么? 服務器對一大堆長行進行排序,然后丟棄幾乎所有的行。 您的其中一columns是 LOB -- 一個 TEXT 列並沒有幫助。

這是一種可以減少這種開銷的方法:通過查找所需的主鍵集來確定所需的行,然后僅獲取這些行的內容。

你想要什么行? 這個子查詢會找到它們。

                  SELECT id
                    FROM logs
                    JOIN logs_pivot 
                            ON logs.id = logs_pivot.log_id
                   WHERE logs_pivot.model_id = 'some_id'
                     AND logs_pivot.model_type = 'My\Class'
                   ORDER BY logs.date DESC
                   LIMIT 50

這完成了計算您想要的行的所有繁重工作。 因此,這是您需要優化的查詢。

可以通過logs上的這個索引來加速

CREATE INDEX logs_date_desc ON logs (date DESC);

以及logs_pivot上的這個三列復合索引

CREATE INDEX logs_pivot_lookup ON logs_pivot (model_id, model_type, log_id);

這個索引可能會更好,因為優化器會看到對logs_pivot的過濾,而不是對logs的過濾。 因此,它將首先查看logs_pivot

或者可能

CREATE INDEX logs_pivot_lookup ON logs_pivot (log_id, model_id, model_type);

先嘗試一個,然后再嘗試另一個,看看哪個會產生更快的結果。 (我不確定 JOIN 將如何使用復合索引。)(或者簡單地添加兩者,然后使用EXPLAIN來查看它使用的是哪一個。)

然后,當您對子查詢的性能感到滿意或滿意時,使用它來獲取所需的行,如下所示

SELECT * 
  FROM logs
  WHERE id IN (
                  SELECT id
                    FROM logs
                    JOIN logs_pivot 
                            ON logs.id = logs_pivot.log_id
                   WHERE logs_pivot.model_id = 'some_id'
                     AND model_type = 'My\Class'
                   ORDER BY logs.date DESC
                   LIMIT 50
              )
  ORDER BY date DESC

這是有效的,因為它對較少的數據進行排序。 logs_pivot上的覆蓋三列索引也將有所幫助。

請注意,子查詢和主查詢都有 ORDER BY 子句,以確保返回的詳細結果集符合您需要的順序。

編輯Darnit,使用 MariaDB 10+ 和 MySQL 8+ 太久了,我忘記了舊的限制。 試試這個。

SELECT * 
  FROM logs
  JOIN (
                  SELECT id
                    FROM logs
                    JOIN logs_pivot 
                            ON logs.id = logs_pivot.log_id
                   WHERE logs_pivot.model_id = 'some_id'
                     AND model_type = 'My\Class'
                   ORDER BY logs.date DESC
                   LIMIT 50
        ) id_set ON logs.id = id_set.id
  ORDER BY date DESC

最后,如果您知道您只關心比某個時間更新的行,您可以將這樣的內容添加到您的子查詢中。

                  AND logs.date >= NOW() - INTERVAL 5 DAY

如果您的表中有大量歷史數據,這將很有幫助。

暫無
暫無

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

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