MySQL: İç sorgularda “ORDER BY” ile UNION'u optimize edin


9

Ben sadece aynı düzen ile birden fazla tablodan oluşan bir günlük sistemi kurduk.

Her veri kaynağı için bir tablo vardır.

Günlük görüntüleyici için şunu istiyorum

  • BİRLİĞİ tüm günlük tabloları ,
  • bunları hesaba göre filtreleyin ,
  • kaynağın tanımlanması için sözde bir sütun ekleyin ,
  • onları zamana göre sırala ,
  • ve sayfalandırma için sınırlandırın .

Tüm tablolar, zeitpunktdizinlenmiş bir tarih / saat sütunu olarak adlandırılan bir alan içerir .

İlk denemem şuydu:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730)

ORDER BY zeit DESC LIMIT 10;

Her iki tablodaki tüm satırlar alt sorgular tarafından döndürüldüğü ve UNION.

Geçici çözümüm şuydu:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

ORDER BY zeit DESC LIMIT 10;

Ben her iki alt sorgu sıralanmış ve UNIONdaha sonra, daha sonra satırları birleştirir ve sıralar önce sınırlı olmalıdır sorgu motoru burada dizinleri kullanacağını bekliyordum .

Gerçekten bu olacağını düşündüm, ama EXPLAINsorgu üzerinde çalışan bana alt sorgu hala her iki tablo arama söylüyor.

EXPLAINingalt sorgular bana istenen optimizasyonu gösterir, ancak UNIONingbirlikte göstermez.

Bir şey mi kaçırdım?

Alt sorguların ORDER BYiçindeki cümlelerin UNIONa olmadan yoksayıldığını biliyorum LIMIT, ama bir sınır var.

Düzenleme:
Aslında, muhtemelenaccount_idkoşulolmadan sorguları olacaktır.

Tablolar zaten var ve verilerle dolu. Kaynağa bağlı olarak düzende değişiklikler olabilir, bu yüzden onları bölmek istiyorum. Ayrıca, günlüğe kaydetme istemcileri bir nedenle farklı kimlik bilgileri kullanır.

Günlük okuyucuları ve gerçek tablolar arasında bir tür katman tutmak zorundayım.

Tüm sorgunun ve ilk alt sorgunun yanı sıra tablo düzeninin ayrıntılı olarak yürütme planları şunlardır:

https://gist.github.com/ca8fc1093cd95b1c6fc0


1
Bunun için en iyi endeks bileşik olacaktır (account_id, zeitpunkt). Böyle bir endeksiniz var mı? İkinci en iyi (zeitpunkt)olanı (sanırım) tek olacaktır - ancak kullanılan verimlilik, satırların ne sıklıkta account_id=730göründüğüne bağlıdır .
ypercubeᵀᴹ

2
Neden UNION DISTINCT? Ek tanımlama sütununa bağlı olarak sonuçlar alt sorgular arasında farklı olacağından, orada bir tür ve farklı zorlamaya gerek yoktur. Kullanın UNION ALL.
ypercubeᵀᴹ

1
@ Ypercube'un önerisine ek olarak, bir sorum var: tüm bu günlüklerin aynı tabloda sourcesütun eklenmesi ile daha iyi olmaz mıydı ? Bu şekilde UNION, tüm verilerinizde s'den kaçınabilir ve dizin (ler) kullanabilirsiniz.
dezso

1
@ypercube Aslında, account_id koşulu olmayan sorgular da olabilir . DISTINCT bayrak bir önceki denemeden soydan gelen ve sonuçlar her zaman farklı olacaktır çünkü aslında işe yaramaz çünkü DISTINCT dafualt davranıştır. Tablolar zaten var ve verilerle dolu. Her neyse, kaynağa bağlı olarak düzende değişiklikler olabilir, bu yüzden onları bölmek istiyorum. Ayrıca, günlüğe kaydetme istemcileri bir nedenle farklı kimlik bilgileri kullanır. Günlük okuyucuları ve gerçek tablolar arasında bir tür katman tutmak zorundayım.
Lukas

Tamam, ancak UNION ALLdeğişik yürütme planı sağlayıp sağlamadığını kontrol edin .
ypercubeᵀᴹ

Yanıtlar:


8

Sadece meraktan, bu sürümü deneyebilir misin? Optimize ediciyi, alt sorguların ayrı kullanacağı endeksleri kullanması için kandırabilir:

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10) 
    AS a

UNION ALL

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)
    AS b

ORDER BY zeit DESC LIMIT 10;

Hala sahip olabileceğiniz en iyi endeksin bileşik olduğunu düşünüyorum (account_id, zeitpunkt). 10 satırı hızlı bir şekilde verir ve hiçbir hile yapmaya gerek kalmaz.


Yaptığınız değişiklik istenen sonuçları getirdi. Teşekkürler! Sadece bir yan not olarak: şimdiye kadar hangi dizinin daha iyi olacağından emin değilim. Her ikisini de kullanabilirim. Kullanıcı sayısının ve log entries / useriradenin nasıl ölçeklendiğini kontrol etmem gerekecek .
Lukas

İle sorgu ve sorgu olmadan ihtiyacınız olacaksa account_id=?, ikisini de saklayın.
ypercubeᵀᴹ

@ypercube, +1 bu çok akıllı ve benim de (benzer) durumumda çalıştı! İlişkilendirilmiş sorguları neden sahte bir SELECT * FROMMySQL hilesi ile indeksleri içine sarmanın açıklayabilir misiniz ?
dkamins

@dkamins: MySQL optimizer çok akıllı değil, genellikle burada olduğu gibi türetilmiş bir tablo olduğunda (SELECT ...) AS a, türetilmiş tabloyu diğer türetilmiş tablolardan ve ardından tüm sorguyu ayrı ayrı değerlendirmeye ve optimize etmeye çalışır.
ypercubeᵀᴹ

@Lukas, Aslında dizinin kullanıldığından emin olmanız gerektiğinden, / add komutunu kullanmak force indexsize daha iyi bir çözüm sağlayacaktır.
Pacerier
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.