SQL RANDOM Fonksiyonu Performans Problemleri
Ahmet Alp Balkan tarafından 8 Eylül 2008 tarihinde yazılmıştır. (Web Teknolojileri) 7 Yorum »
Şimdilik böyle bilimsel makaleler yazabilecek biri değilim. Ama ileride belki birilerinin işine yarama ihtimalini düşünecek olursak Internet’te hep yiyen olmaktansa biraz da üretmek gerek
Biraz da Türkçe kaynaklarda bu sorunu kimsenin dile getirmediğini farkettim.
Sorun: Öncelikle problem herhangi bir veritabanı sisteminin çok sayıda kaydı veritabanından çekerken kayıtların rastgele dizilmesi için ORDER clause içinde kullanılan SQL RANDOM komutunun doğurduğu performans problemleri.
Açıklama: SQL’de kullanılan ORDER tümceciğinin içinde bir sıralama fonksiyonu olarak geçen RAND (veya RANDOM) çeşitli SQL yapılarında şu şekilde kullanılıyor:
SELECT column FROM table ORDER BY RAND() LIMIT 1 // MYSQL
SELECT column FROM table ORDER BY RANDOM() LIMIT 1 // POSTGRESQL
SELECT TOP 1 column FROM table ORDER BY NEWID() // MICROSOFT SQL SERVER
Bu şekilde veritabanından alınacak veri için tüm tablodaki veriler LIMIT veya TOP gibi komutlara bakılmadan SQL tarafından teker teker sıraya diziliyor ve seçilen column (sütun) değerleri ve kayıtların rastgele dizilmiş yeni sıraları, mevcut bağlantı kapatılmadığı sürece hafızada tutuluyor. Fakat bu işlem, çok sayıda kayıta sahip tablolardan (en az 80.000 gibi bir sayıda elle tutulur sonuçlar elde ediliyor) yapılan rastgele kayıt alma sorgularında büyük performans-hız problemlerine ve sunucuda büyük bir yüke neden oluyor. Bunun nedeni ise öncelikle LIMIT komutunun değil, RANDOM ifade içeren ORDER clause’un çalıştırılması. Mantıklı olan da bu aslında, yani SQL doğru olanı yapıyor. Diğer türlü LIMIT-TOP ifadelerine 10 değerini verip sonra RANDOM komutunun çalışmasını beklersek ancak id veya kayıt sırası gibi değerlere göre ilk 10 kaydı çekip bunları sıralama yapmasını beklememiz gerekirdi.
Alternatif Çözümler : Dediğim gibi, on binlerce kayıt arasında işlem yapmıyorsanız, öncelikle bu probleme takılmadan yukarıdaki ORDER by RANDOM ifadesini rahatlıkla kullanabilirsiniz. Fakat alacağınız tek bir satır olsa bile, on binlerce kayıt barındıran bir tablodan bu komutla kayıt çekmek performans yönünden akıllıca değil. Buna çözüm olarak gösterebilecek en mantıklı çözüm, RANDOM komutunu doğrudan bir column gibi SELECT statement içine yerleştirmek. Aşağıdaki üç sorgunun benchmark sonuçları sanırım durumu anlatıyor.
* Sorgu 1. SELECT … ORDER BY RAND()
* Sorgu 2. SELECT RAND() * MAX(ID) …
* Sorgu 3. SELECT RAND() * MAX(ID) + ORDER BY ID …
-------------------------------------------------------------------- Kayıt* || 100 || 1.000 || 10.000 || 100.000 || 1.000.000 -------------------------------------------------------------------- Sorgu 1|| 0:00.718s || 0:02.092s || 0:18.684s || 2:59.081s || 58:20.000s Sorgu 2|| 0:00.519s || 0:00.607s || 0:00.614s || 0:00.628s || 0:00.637s Sorgu 3|| 0:00.570s || 0:00.607s || 0:00.614s || 0:00.628s || 0:00.637s *Her sorgu 1000 defa çalıştırılmıştır
Görüldüğü gibi RANDOM komutunun, ORDER tümcesi içine yerleştirilmesi, 1 milyon kayıtta 0.6 s. yerine 58.2 saniye gibi inanılmaz bir performans kaybı ve gereksiz yüke neden oluyor.
Bunun yanında ID kesintisi yaşamayacak olan sistemlerden son ID yi alıp ona kadar olan sayılardan N tane üretilip ‘where id=3 or id=5..’ gibi şekillerde de sonuçlar alınabilir. Fakat bu, kayıtların silinebildiği sistemlerde sağlıklı çalışmayabilir, eksik sayıda sonuç döndürebilir.
Sonuç: SQL RANDOM fonksiyonunu, büyük sayılarda kayıt içeren tablolarda ORDER cümleciği içince kullanmak, büyük performans problemlerine neden oluyor. Bu nedenle RANDOM fonskiyonu, doğrudan SELECT statement içinde kullanılmalıdır.

İlk paragraftaki “kimse” yerine “çok az kişi” kullansak nasıl olur?
ben şurada bahsetmiştim bu konudan, php ve mysql için bir de çözüm önerisi mevcut.
Burada da dmry.net’de aynı konu işlenmişti.
@Emre benim değinmek istediğim, “SQL RANDOM fonksiyonunu, büyük sayılarda kayıt içeren tablolarda ORDER cümleciği içince kullanmak, büyük performans problemlerine neden oluyor. konusuydu. Ondan böyle daraltma ihtiyacı duydum. Zaten yazı içindeki linklerde, sitenizdeki örneklerin benzerleri mevcut.
dediğin gibi kayıt silinmesi durumlarında hep problem çıkaracaktır.
toplam kayıt sayısını bulup, rastgele sayıyı dışarıda üretip limite (limit $rand, 1) parametre olarak verebiliriz. Kayıt sayısı bulmak için ekstra 1 sorgu daha çalıştıracağız ama herhalde 58,2 sn. tutmaz toplamda
@Murat, evet öyle de olabilir, fakat yine de LIMIT’i uygulayana kadar önce ORDER uygulanıyor. Yani yukarıda belirttiğimiz zaafiyete neden oluyor. En mantıklısı yazıda verdiğim alternatif sorguları kullanmak.
demek istediğim ;
select count(id) as idx …. deyip sonucu $rand değişkenine atarız. sonrada
select * from tablo LIMIT $rand,1
deriz, rastgele bir kayıt gelir, sadece 1 sorgu fazladan çalışır. verdiğin alternatif sorgular kayıt silinmesi durumunda hata verecek, silinmeyen bir kayıt bulana kadar sorguyu yinelemek zorunda kalacağız.
saygılar
Performans açısından verdiğiniz örnekler gayet mantıklı. Teşekkürler. Diğer sitelerde genelde düz mantık verilmiş. Bu tip özelleşmiş çözümleri herkes yazmıyor maalesef.
SELECT * FROM table_name ORDER BY RAND() LIMIT 10
seklinde bir sorgu calistiriyorum
sonuclarımda sürekli ilk başta 1 tane boş kayıt alıyorum.. bir türlü işin içinden çıkamadım