MYSQL yüksek LIMIT ofseti sorguyu neden yavaşlatıyor?


174

Kısaca senaryo: 16 milyondan fazla kaydı olan [2GB boyutunda] bir tablo. SELECT ile daha yüksek LIMIT ofseti, ORDER BY * birincil_anahtarı * kullanılırken sorgu yavaşlar

Yani

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

daha az alır

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

Bu sadece 30 kayıt sipariş ediyor ve aynı şekilde. Yani ORDER BY'nin genel yükü değil.
Şimdi son 30 satırı getirirken yaklaşık 180 saniye sürüyor. Bu basit sorguyu nasıl optimize edebilirim?


NOT: Ben yazarım. MySQL, yukarıdaki durumlarda dizine (PRIMARY) başvurmaz. açıklama için kullanıcı "Quassnoi" tarafından aşağıdaki bağlantıya bakın.
Rahman

Yanıtlar:


197

Sorgunun ilk OFFSET + LIMITkayıtları geri sayması (ve yalnızca LIMITbunlardan alması ) gerektiğinden, yüksek ofsetlerin sorguyu yavaşlatması normaldir . Bu değer ne kadar yüksek olursa, sorgu o kadar uzun çalışır.

Sorgu doğrudan doğruya gidemez OFFSETçünkü ilk olarak kayıtlar farklı uzunlukta olabilir ve ikinci olarak silinen kayıtlardan boşluklar olabilir. Her kaydı kontrol etmek ve saymak gerekir.

Bir tablonun idbir olduğunu varsayarsak, bu hileyi kullanarak tabloyu hızlandırabilirsiniz:PRIMARY KEYMyISAM

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

Bu makaleye bakın:


7
MySQL "erken satır arama" davranışı neden bu kadar uzun konuştuğuna cevaptı. Sağladığınız hile ile, yalnızca eşleşen kimlikler (doğrudan dizine göre) bağlanır ve çok fazla kaydın gereksiz satır aramalarını kaydeder. Hile yaptı, Yaşasın!
Rahman

4
@harald: "çalışmıyor" demekle tam olarak ne demek istiyorsun? Bu saf bir performans iyileştirmesidir. Kullanılabilir bir dizin ORDER BYyoksa veya dizin ihtiyacınız olan tüm alanları kapsıyorsa, bu geçici çözüm gerekmez.
Quassnoi

6
@ f055: cevap "hızlandır" diyor, "anında yap" değil. Cevabın ilk cümlesini okudunuz mu?
Quassnoi

3
InnoDB için böyle bir şey çalıştırmak mümkün mü?
NeverEndingQueue

3
@Lanti: lütfen ayrı bir soru olarak gönderin ve ile etiketlemeyi unutmayın postgresql. Bu MySQL'e özel bir cevaptır.
Quassnoi

221

Ben de aynı problemi yaşadım. Belirli bir 30 kümesi değil, bu verilerin büyük bir miktarını toplamak istediğiniz göz önüne alındığında, muhtemelen bir döngü çalıştırıyor ve ofseti 30 oranında artırıyorsunuz.

Bunun yerine ne yapabilirsiniz:

  1. Bir veri kümesinin son kimliğini tutun (30) (örn. LastId = 530)
  2. Koşulu ekle WHERE id > lastId limit 0,30

Böylece her zaman bir ZERO ofsetine sahip olabilirsiniz. Performans iyileştirmesinden şaşıracaksınız.


Boşluklar varsa bu işe yarar mı? Tek bir benzersiz anahtarınız yoksa (örneğin bileşik bir anahtar) yoksa ne olur?
xaisoft

8
Bunun, yalnızca sonuç kümeniz bu tuşa göre artan sırada sıralandığında (aynı fikrin işe yaraması için azalan düzende, ancak> lastid'i <lastid olarak değiştirmesi durumunda) işe yaramayacağı açık olmayabilir. birincil anahtar veya başka bir alan (veya alan grubu)
Eloff

Aferin o adam!
Sorunumu

30
Sınırlı / ofsetin genellikle sayfalandırılmış sonuçlarda kullanıldığını ve lastId'i tutmanın, kullanıcının her zaman bir sonraki sayfaya değil, herhangi bir sayfaya atlayabileceği anlamına gelmediğini unutmayın. Başka bir deyişle, ofsetin sürekli bir desen izlemek yerine genellikle sayfa ve sınıra göre dinamik olarak hesaplanması gerekir.
Tom


17

MySQL doğrudan 10000. kayda (veya önerdiğiniz gibi 80000 bayt) gidemez, çünkü bu şekilde paketlendiğini / sipariş edildiğini (veya 1 ila 10000'de sürekli değerlere sahip olduğunu) varsayamaz. Aslında bu şekilde olsa da, MySQL hiçbir delik / boşluk / silinmiş kimlik olmadığını varsayamaz.

Dolayısıyla, bobs'un belirttiği gibi, idgeri dönecek 30'u bulmadan önce MySQL'in 10000 satır getirmesi (veya dizinin 10000. girişinden geçmesi) gerekecektir .

EDIT : benim açımdan göstermek için

Her ne kadar

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

olur yavaş (er) ,

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

olacağını hızlı (er) ve aynı sonuçları hiçbir kayıp vardır şartıyla döneceğini ids (yani boşluklar).


2
Doğru. Ancak "id" ile sınırlı olduğundan, bu kimlik bir dizin (birincil anahtar) içindeyken neden bu kadar uzun sürüyor? Optimizer doğrudan bu dizine başvurmalı ve ardından eşleşen kimliklere sahip satırları getirmelidir (bu dizinden gelir)
Rahman

1
Kimlikte bir WHERE yan tümcesi kullandıysanız, doğrudan bu işarete gidebilir. Bununla birlikte, id tarafından sipariş edilen bir sınır koyarsanız, bu sadece başlangıç ​​için göreceli bir sayaçtır, bu yüzden tüm yolu çaprazlamak zorundadır.
Riedsio

Çok iyi bir makale eversql.com/…
Pažout

@Riedsio benim için çalıştı Teşekkürler.
mahesh kajale

8

SELECT sorgularını ORDER BY id LIMIT X, Y optimize etmek için ilginç bir örnek buldum. 35 milyon satırım var, bu yüzden bir dizi satır bulmak 2 dakika sürdü.

İşte hile:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

Sadece NEREDE aldığınız son kimliği ile performansı çok artırın. Benim için 2 dakika ile 1 saniye arasındaydı :)

Diğer ilginç numaralar: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Dizelerle de çalışır


1
bu yalnızca hiçbir verinin silinmediği tablolar için geçerlidir
miro

1
@miro Sadece sorgunuzun rastgele posterlerde arama yapabileceği varsayımı altında çalışıyorsanız, bu posterin varsaydığına inanmıyorum. Çoğu gerçek dünya vakası için bu yöntemi sevmiyorum, ancak her zaman elde edilen son kimliği temel aldığınız sürece boşluklarla çalışacaktır.
Gremio

5

İki sorgunun zaman alıcı kısmı satırları tablodan almaktır. Mantıken konuşursak, LIMIT 0, 30versiyonda sadece 30 satırın alınması gerekir. In LIMIT 10000, 30sürümü, 10000 satır değerlendirilir ve 30 satır döndürülür. Veri okuma sürecimde bazı optimizasyonlar yapılabilir, ancak aşağıdakileri göz önünde bulundurun:

Sorgularda WHERE yan tümcesi varsa ne olur? Motor, hak kazanan tüm satırları döndürmeli ve ardından verileri sıralamalı ve son olarak 30 satırı almalıdır.

Ayrıca, satırların ORDER BY sırasında işlenmediği durumu da göz önünde bulundurun. Hangi sıraların döndürüleceğini belirlemek için tüm uygun satırlar sıralanmalıdır.


1
sadece bu 10000 satırı getirmenin neden zaman harcadığını merak ediyorum. Bu alanda kullanılan dizin (birincil anahtar olan id), bu satırları almayı, kayıt no için PK dizinini aramak kadar hızlı hale getirmelidir. 10000, bu da dosyayı ofsete aramak kadar endeks kayıt uzunluğu ile çarpılarak hızlı olması gerekiyordu (yani, 10000 * 8 = bayt no 80000 aramak - 8'in indeks kayıt uzunluğu olduğu göz önüne alındığında)
Rahman

@Rahman - 10000 satırı geçmenin tek yolu, üst üste adım atmaktır. Bu olabilir sadece bir dizin dahil, ama yine de endeks satırlar aracılığıyla adım için zaman ayırın. Orada hiçbir doğru (her durumda) "aramak" olabilir kaydetmek için MyISAM veya InnoDB'nin yapı 10000 10000 * 8 öneri varsayar (1) MyISAM (2) SABİT uzunluk kayıt ve (3) tablodan asla herhangi siler . Her neyse, MyISAM dizinleri BTrees'tir, bu yüzden işe yaramaz.
Rick James

Bu cevabın belirttiği gibi, inanıyorum ki, gerçekten yavaş olan kısım, dizinleri (tabii ki de toplanacak, ancak diskteki satır aramaları kadar yakın bir yerde) geçmeyecek şekilde sıra aramasıdır. Bu sorun için sağlanan geçici çözüm sorgularına dayanarak, dizin aralığının dışındaki sütunları seçerseniz satır aramaları gerçekleşme eğiliminde olduğuna inanıyorum. Bunun neden gerekli olduğuna dair bir neden bulamadım, ancak bazı geçici çözümlerin neden yardımcı olduğu anlaşılıyor.
Gremio

1

Bir karşılaştırma ve rakamlarla ilgilenenler için :)

Deney 1: Veri kümesi yaklaşık 100 milyon satır içerir. Her satır birkaç BIGINT, TINYINT ve yaklaşık 1k karakter içeren iki METİN alanı (kasıtlı olarak) içerir.

  • Mavi: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Turuncu: = @ Quassnoi'nin yöntemi. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Tabii ki, üçüncü yöntem, ... WHERE id>xxx LIMIT 0,5sabit zaman olması gerektiğinden burada görünmüyor.

Deney 2: Benzer bir şey, tek bir satırda sadece 3 BIGINTs var.

  • yeşil: = önceki mavi
  • kırmızı: = önceki turuncu

resim açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.