![](/img/trans.png)
[英]SELECT from two tables WHERE different columns in each table equal $id ORDER BY common column (PHP/MySQL)
[英]where like and order by on different tables/columns
有關信息,在以下示例中, big_table
由數百萬行組成,而small_table
由數百行組成。
這是我要執行的基本查詢:
SELECT b.id
FROM big_table b
LEFT JOIN small_table s
ON b.small_id=s.id
WHERE s.name like 'something%'
ORDER BY b.name
LIMIT 10, 10;
這很慢,我可以理解為什么兩個索引都不能使用。
我最初的想法是將查詢分為多個部分。
這很快:
SELECT id FROM small_table WHERE name like 'something%';
這也很快:
SELECT id FROM big_table WHERE small_id IN (1, 2) ORDER BY name LIMIT 10, 10;
但是,放在一起,它變得很慢:
SELECT id FROM big_table
WHERE small_id
IN (
SELECT id
FROM small_table WHERE name like 'something%'
)
ORDER BY name
LIMIT 10, 10;
除非對每行都重新評估子查詢,否則它不應該比單獨執行兩個查詢慢嗎?
我正在尋找優化初始查詢的任何幫助,並理解為什么第二個查詢不起作用。
搜尋最后一個查詢的結果:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
| 1 | PRIMARY | small_table | range | PRIMARY, ix_small_name | ix_small_name | 768 | NULL | 1 | Using where; Using index; Using temporary; Using filesort |
| 1 | PRIMARY | big_table | ref | ix_join_foreign_key | ix_join_foreign_key | 9 | small_table.id | 11870 | |
臨時解決方案:
SELECT id FROM big_table ignore index(ix_join_foreign_key)
WHERE small_id
IN (
SELECT id
FROM small_table ignore index(PRIMARY)
WHERE name like 'something%'
)
ORDER BY name
LIMIT 10, 10;
(結果和解釋與EXISTS而不是IN相同)
EXPLAIN輸出變為:
| 1 | PRIMARY | big_table | index | NULL | ix_big_name | 768 | NULL | 20 | |
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 8 | func | 1 | |
| 2 | MATERIALIZED | small_table | range | ix_small_name | ix_small_name | 768 | NULL | 1 | Using where; Using index |
如果有人有更好的解決方案,我仍然很感興趣。
您正在尋找一個EXISTS
或IN
查詢。 由於眾所周知MySQL在IN
較弱,因此我會嘗試EXISTS
,盡管更喜歡IN
因為它的簡單性。
select id
from big_table b
where exists
(
select *
from small_table s
where s.id = b.small_id
and s.name = 'something%'
)
order by name
limit 10, 10;
在big_table
上具有良好的索引將很有幫助。 它應該首先包含small_id
以找到匹配項,然后包含排序name
。 據我所知,該ID自動包含在MySQL索引中(否則也應將其添加到索引中)。 因此,您將擁有一個索引,該索引按所需順序包含big_table
所需的所有字段(稱為覆蓋索引),因此可以從索引中單獨讀取所有數據,而不必訪問表本身。
create index idx_big_quick on big_table(small_id, name);
您面臨的問題是您在小表上有條件,但試圖避免在大表中進行排序。 在MySQL中,我認為您至少需要進行全表掃描。
第一步是使用exists
編寫查詢,正如其他人提到的那樣:
SELECT b.id
FROM big_table b
WHERE EXISTS (SELECT 1
FROM small_table s
WHERE s.name LIKE 'something%' AND s.id = b.small_id
)
ORDER BY b.name;
問題是:您可以欺騙MySQL使用索引執行ORDER BY
嗎? 一種可能性是使用適當的索引。 在這種情況下,適當的索引是: big_table(name, small_id, id)
和small_table(id, name)
。 索引中鍵的順序很重要。 因為第一個是覆蓋索引,所以MySQL可能會按名稱順序依次讀取索引,選擇適當的ID。
您可以嘗試以下方法:
SELECT b.id
FROM big_table b
JOIN small_table s
ON b.small_id = s.id
WHERE s.name like 'something%'
ORDER BY b.name;
要么
SELECT b.id FROM big_table b
WHERE EXISTS(SELECT 1 FROM small_table s
WHERE s.name LIKE 'something%' AND s.id = b.small_id)
ORDER BY b.name;
注意:您似乎不需要LEFT JOIN
。 左外部big_table
幾乎總是會導致對big_table
進行全表掃描
PS確保您在big_table.small_id
上有一個索引
計划A
SELECT b.id
FROM big_table b
JOIN small_table s ON b.small_id=s.id
WHERE s.name like 'something%'
ORDER BY b.name
LIMIT 10, 10;
(請注意刪除LEFT
。)
你需要
small_table: INDEX(name, id)
big_table: INDEX(small_id), or, for 'covering': INDEX(small_id, name, id)
它將使用s
索引查找'something%'
並逐步瀏覽。 但是它必須找到所有這樣的行,然后將JOIN
到b
才能在那里找到所有這樣的行。 只有這樣,它才能執行ORDER BY
, OFFSET
和LIMIT
。 將會有一個文件排序( 可能發生在RAM中)。
索引中的列順序很重要。
計划B
另一個建議可能效果很好。 這取決於各種事情。
SELECT b.id
FROM big_table b
WHERE EXISTS
( SELECT *
FROM small_table s
WHERE s.name LIKE 'something%'
AND s.id = b.small_id
)
ORDER BY b.name
LIMIT 10, 10;
這需要這些:
big_table: INDEX(name), or for 'covering', INDEX(name, small_id, id)
small_table: INDEX(id, name), which is 'covering'
(注意:如果您執行的操作不是SELECT b.id
,那么我對覆蓋的評論可能是錯誤的。)
哪個更快(A或B)? 如果不了解“某物百分比”的頻率以及多對一映射的“多少”,就無法預測。
設定值
如果這些表是InnoDB,請確保將innodb_buffer_pool_size
設置為可用 RAM的大約70%。
分頁
您對OFFSET
使用是否意味着您正在“分頁”數據? OFFSET
是一種低效的方法。 請參閱我的博客 ,但是請注意,只有Plan B可以使用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.