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.


7 Yorum

  1. 1 Emre Erkan  (8 Eylül 2008 21:16)

    İ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.

  2. 2 Ahmet Alp Balkan  (8 Eylül 2008 22:09)

    @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.

  3. 3 Murat Gazioğlu  (20 Eylül 2008 14:03)

    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 :)

  4. 4 Ahmet Alp Balkan  (20 Eylül 2008 17:02)

    @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.

  5. 5 Murat Gazioğlu  (22 Eylül 2008 11:46)

    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

  6. 6 Murat Topçu  (14 Ağustos 2009 18:47)

    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.

  7. 7 kadir  (27 Şubat 2010 02:43)

    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

Yazıyla ilgili Yorumunuzu Bırakın