Veritabanı Yönetimi

    MySQL Full-Text Arama Kurulumu

    FULLTEXT index kurma, MATCH AGAINST sorguları ve Türkçe metinde karşılaşılan tuzaklar.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Bir ürün kataloğunda ya da makale arşivinde arama kutusu yaptığında ilk refleks WHERE baslik LIKE '%kelime%' yazmaktır. Birkaç bin satıra kadar sorun çıkmaz; on binlere geldiğinde arama saniyelerce sürmeye başlar. Sebebi basit: öne joker konmuş bir LIKE sorgusu hiçbir B+Tree index'ini kullanamaz ve tabloyu baştan sona tarar. MySQL full-text arama, tam olarak bu sorunu çözmek için tasarlanmış ayrı bir index türüdür.

    Bu rehberde FULLTEXT index'in nasıl oluşturulacağını, MATCH ... AGAINST sorgusunun üç farklı modunu, alaka puanının nasıl hesaplandığını ve gerçek hayatta en çok baş ağrıtan konuyu — Türkçe metinlerde kelime ayırma ve minimum kelime uzunluğu ayarlarını — anlatacağım. Sonunda kendi arama kutunu LIKE yerine düzgün bir arama motoruyla besleyebilecek, ne zaman MySQL'in yetmediğini ve ayrı bir arama motoruna geçmen gerektiğini de bilecek durumda olacaksın.

    FULLTEXT Index Nedir ve Nasıl Çalışır#

    FULLTEXT index, klasik bir B+Tree değil ters index (inverted index) yapısıdır. Metni kelimelere böler ve her kelime için "bu kelime hangi satırlarda, kaç kez, hangi konumda geçiyor" bilgisini tutar. Arama yaptığında tablo taranmaz; doğrudan kelime listesinden eşleşen satır kimlikleri okunur. Bu yüzden metin uzunluğundan bağımsız olarak hızlıdır.

    MySQL 5.6 ve sonrasında FULLTEXT index'ler InnoDB tablolarda da desteklenir; öncesinde yalnızca MyISAM'da vardı ve bu yüzden eski projelerde metin tabloları hâlâ MyISAM olarak bırakılmış olabilir. Bugün böyle bir zorunluluk yok, InnoDB kullanmalısın; iki motorun farkını InnoDB ve MyISAM karşılaştırması yazısında ele aldım.

    CREATE TABLE makale (
      id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
      baslik  VARCHAR(255) NOT NULL,
      icerik  MEDIUMTEXT   NOT NULL,
      yayin   DATETIME     NOT NULL,
      PRIMARY KEY (id),
      FULLTEXT KEY ft_makale (baslik, icerik)   -- iki kolon tek index'te
    ) ENGINE=InnoDB;
    

    Var olan bir tabloya sonradan da ekleyebilirsin. Büyük tablolarda bu işlem uzun sürer, çünkü tüm metin taranıp kelime listesi kurulur:

    ALTER TABLE makale ADD FULLTEXT KEY ft_makale (baslik, icerik);
    

    Dikkat edilecek en önemli nokta şudur: MATCH ifadesindeki kolon listesi, index'teki kolon listesiyle birebir aynı olmalıdır. FULLTEXT (baslik, icerik) index'i varken MATCH(baslik) AGAINST(...) yazarsan MySQL hata verir ya da index'i kullanamaz. Farklı kombinasyonlarda arama yapacaksan ayrı index'ler oluşturman gerekir.

    MATCH AGAINST: Üç Arama Modu#

    Aramanın üç modu vardır ve her biri farklı bir kullanım senaryosuna hizmet eder.

    Doğal dil modu (varsayılan). Kullanıcının yazdığı cümleyi kelimelere böler ve alaka puanına göre sıralar. En sık kullanılan moddur.

    SELECT id, baslik,
           MATCH(baslik, icerik) AGAINST('sunucu güvenliği') AS puan
    FROM makale
    WHERE MATCH(baslik, icerik) AGAINST('sunucu güvenliği')
    ORDER BY puan DESC
    LIMIT 20;
    

    Boolean modu. Operatörlerle hassas kontrol sağlar; kullanıcıya gelişmiş arama sunuyorsan bu moddasın.

    OperatörAnlamıÖrnek
    +Kelime mutlaka bulunmalı+sunucu +guvenlik
    -Kelime bulunmamalı+sunucu -windows
    *Sonuna joker (önek araması)guven*
    "..."Tam ifade araması"sanal sunucu"
    ~Puanı düşürür ama elemezsunucu ~eski
    > <Puana etkiyi artırır/azaltırsunucu >hizli
    ( )Gruplama+sunucu +(vds vps)
    -- "sunucu" zorunlu, "windows" istenmiyor, "guven" ile başlayan kelimeler kabul
    SELECT id, baslik
    FROM makale
    WHERE MATCH(baslik, icerik)
          AGAINST('+sunucu -windows guven*' IN BOOLEAN MODE)
    LIMIT 20;
    

    Boolean modunun önemli bir farkı vardır: sonuçlar varsayılan olarak alaka puanına göre sıralanmaz. Sıralama istiyorsan ORDER BY ile açıkça belirtmen gerekir.

    Sorgu genişletme modu. İlk aramanın en alakalı sonuçlarındaki kelimeleri alıp ikinci bir arama yapar. Kullanıcı çok kısa terim yazdığında kapsamı genişletir ama alakasız sonuç üretme riski yüksektir, dikkatli kullan.

    SELECT id, baslik FROM makale
    WHERE MATCH(baslik, icerik) AGAINST('vds' WITH QUERY EXPANSION)
    LIMIT 20;
    

    Alaka Puanı Nasıl Hesaplanır ve Nasıl Ayarlanır#

    MATCH ... AGAINST ifadesini SELECT listesine koyduğunda bir kayan noktalı puan döner. Bu puan kabaca terim sıklığı ile ters belge sıklığının bileşimidir: kelime o satırda ne kadar sık geçiyorsa puan artar, kelime tablodaki satırların çoğunda geçiyorsa puan düşer. Yani "ve", "bir" gibi her yerde geçen kelimeler doğal olarak değersizleşir.

    Puanı sıfır olan satırlar sonuç kümesine girmez, bu yüzden WHERE içindeki MATCH bir filtre görevi görür. Aynı ifadeyi iki kez yazmak maliyet yaratmaz; MySQL optimizer bunu tek bir hesaplamaya indirger.

    Pratikte tek bir puan yetmez; başlıkta geçen kelimenin içerikte geçenden daha değerli olmasını istersin. Bunu ayrı index'ler ve ağırlıklandırma ile yaparsın:

    ALTER TABLE makale
      ADD FULLTEXT KEY ft_baslik (baslik),
      ADD FULLTEXT KEY ft_icerik (icerik);
    
    SELECT id, baslik,
           (MATCH(baslik) AGAINST('sunucu güvenliği') * 3) +
            MATCH(icerik) AGAINST('sunucu güvenliği')        AS puan
    FROM makale
    WHERE MATCH(baslik) AGAINST('sunucu güvenliği')
       OR MATCH(icerik) AGAINST('sunucu güvenliği')
    ORDER BY puan DESC
    LIMIT 20;
    

    Tazelik gibi ek sinyalleri de puana katabilirsin; örneğin son bir yılda yayınlanan içeriğe küçük bir bonus vermek arama kalitesini belirgin biçimde artırır. Sorgunun gerçekten index kullandığını EXPLAIN ile doğrula; type: fulltext görmelisin. Plan okumanın ayrıntısı MySQL EXPLAIN ile sorgu analizi yazısında.

    Türkçe Metin, Minimum Kelime Uzunluğu ve Stopword#

    Burası sahada en çok zaman kaybettiren bölümdür. Varsayılan yapılandırmada MySQL, InnoDB için üç karakterden kısa kelimeleri index'e hiç almaz. Yani "SSD", "RAM" gibi terimler bulunur ama iki harfli bir marka veya model kodu asla bulunmaz.

    [mysqld]
    innodb_ft_min_token_size = 2   ; InnoDB için (varsayılan 3)
    ft_min_word_len          = 2   ; MyISAM için (varsayılan 4)
    innodb_ft_max_token_size = 84
    

    Bu değeri değiştirdikten sonra MySQL'i yeniden başlatman ve index'i yeniden oluşturman şarttır; aksi hâlde eski index eski kurallarla kalır:

    -- Ayarı değiştirdikten sonra index'i yeniden inşa et
    ALTER TABLE makale DROP INDEX ft_makale;
    ALTER TABLE makale ADD FULLTEXT KEY ft_makale (baslik, icerik);
    -- veya
    OPTIMIZE TABLE makale;
    

    İkinci konu stopword listesidir. MySQL'in yerleşik listesi İngilizce kelimelerden oluşur, yani Türkçe "ve", "ile", "için" gibi kelimeler index'e girer ve gereksiz yer kaplar. Kendi listeni tanımlayabilirsin:

    -- Kendi stopword tablonu oluştur (tek kolon, value adında)
    CREATE TABLE tr_stopword (value VARCHAR(30)) ENGINE=InnoDB;
    INSERT INTO tr_stopword (value) VALUES ('ve'),('ile'),('için'),('bir'),('bu'),('da'),('de');
    
    [mysqld]
    innodb_ft_server_stopword_table = 'sirket/tr_stopword'
    

    Üçüncü ve en can sıkıcı konu Türkçe karakter davranışıdır. MySQL'in kelime ayırıcısı Unicode harfleri tanır, dolayısıyla "güvenlik" kelimesi bütün olarak index'e girer. Ancak kullanıcı "guvenlik" yazarsa eşleşme olmaz, çünkü aksan/şapka normalizasyonu yapılmaz. İki pratik çözüm vardır: ya arama kutusundan gelen metni uygulamada normalize edip ayrı bir "aranabilir" kolona da yazarsın, ya da kolon karşılaştırma kuralını (collation) aksan duyarsız bir seçenekle kurarsın. Ayrı kolon yaklaşımı daha öngörülebilirdir:

    ALTER TABLE makale
      ADD COLUMN baslik_norm VARCHAR(255) NOT NULL DEFAULT '',
      ADD FULLTEXT KEY ft_norm (baslik_norm);
    -- Uygulama, baslik alanını Türkçe karakterlerden arındırıp buraya yazar
    

    Son olarak, kelime köküne inme (stemming) MySQL'de yoktur. "sunucular" araması "sunucu" kayıtlarını bulmaz. Boolean modda sunucu* gibi önek jokeri kısmi bir çözüm sağlar ama Türkçenin eklemeli yapısı düşünüldüğünde sınırlıdır; gerçek kök analizi istiyorsan ayrı bir arama motoruna ihtiyacın olur.

    Bakım, Sınırlar ve İzleme#

    FULLTEXT index'lerin kendine özgü bir bakım ihtiyacı vardır. InnoDB, silinen satırların kelime girdilerini hemen temizlemez; bunları bir "silinmiş" tablosunda tutar ve zamanla index şişer. Düzenli olarak sıkıştırman gerekir:

    [mysqld]
    innodb_optimize_fulltext_only = ON   ; OPTIMIZE yalnızca FT index'i işlesin
    
    SET GLOBAL innodb_optimize_fulltext_only = ON;
    OPTIMIZE TABLE makale;
    SET GLOBAL innodb_optimize_fulltext_only = OFF;
    

    Index'in içine bakmak istersen teşhis tablolarını kullanabilirsin; hangi kelimelerin nasıl kaydedildiğini görmek Türkçe sorunlarını çözerken çok işe yarar:

    SET GLOBAL innodb_ft_aux_table = 'sirket/makale';
    SELECT word, doc_count, doc_id, position
    FROM information_schema.innodb_ft_index_table
    LIMIT 20;
    

    Bilmen gereken sınırlar şunlardır:

    KonuDurum
    Kelime kökü analizi (stemming)Yok
    Yazım hatası toleransı (fuzzy)Yok
    Eş anlamlı sözlükYok, uygulamada çözülür
    Bölümlenmiş (partitioned) tabloFULLTEXT desteklenmez
    Yazma maliyetiNormal index'ten belirgin biçimde yüksek
    Çok dilli tek indexSorunlu; dil başına ayrı kolon önerilir

    Partitioning kısıtı özellikle önemlidir: bölümlenmiş bir tabloya FULLTEXT index ekleyemezsin. Büyük arşiv tablolarını bölmeyi düşünüyorsan bu iki özelliği aynı tabloda kullanamayacağını baştan bilmelisin; ayrıntısı MySQL partitioning ile tablo bölümleme yazısında.

    MySQL Yetmediğinde: Ne Zaman Ayrı Arama Motoru#

    MySQL full-text araması, orta ölçekli ve tek dilli içerikler için fazlasıyla yeterlidir; ayrı bir servis kurmak, yönetmek ve senkron tutmak gerekmediği için toplam maliyeti çok düşüktür. Ama şu ihtiyaçlardan biri belirdiğinde sınırına gelirsin: yazım hatası toleransı, kelime kökü analizi, eş anlamlı yönetimi, faceted (kırılımlı) filtreleme, milyonlarca belgede alt saniye yanıt ya da yazma yükünün arama index'inden tamamen ayrılması.

    Karar verirken şu sırayı izlemeni öneririm: önce MySQL FULLTEXT ile başla ve ölç. Aramalar yüz milisaniyenin altında dönüyorsa ve kullanıcı şikâyeti yoksa başka bir şeye ihtiyacın yok. Yavaşlama başladığında önce arama yapılan kolonları daralt, gereksiz metni index dışında bırak ve OPTIMIZE TABLE ile bakım yap. Bunlar da yetmiyorsa ayrı bir arama motoruna geçiş planla; o noktada asıl iş, veriyi iki sistem arasında tutarlı tutacak senkronizasyon mekanizmasını kurmaktır.

    Bir ara çözüm olarak, aranabilir metni ayrı ve dar bir tabloda toplamak da işe yarar: ana tablo geniş kalır, arama tablosu yalnızca kimlik ve normalize edilmiş metin taşır. Böylece FULLTEXT index küçülür, bakım hızlanır ve yazma maliyeti ana tabloya yayılmaz.

    Sıkça Sorulan Sorular#

    FULLTEXT index LIKE aramasından ne kadar hızlıdır#

    Fark tablo büyüdükçe açılır. Birkaç bin satırda ikisi de anlık görünür; yüz binlerce satırlık bir metin tablosunda LIKE '%kelime%' saniyelerce sürerken FULLTEXT araması genelde milisaniyeler mertebesinde kalır. Sebep algoritmiktir: LIKE her satırı okuyup içinde arar, FULLTEXT ise kelimeden satıra giden hazır bir liste kullanır ve hiç tam tarama yapmaz.

    Türkçe karakterler full-text aramada sorun çıkarır mı#

    Kelimenin kendisi doğru şekilde index'e girer, yani "güvenlik" araması "güvenlik" kayıtlarını bulur. Sorun kullanıcının Türkçe karakter kullanmadan yazmasıdır: "guvenlik" araması eşleşmez, çünkü MySQL aksan normalizasyonu yapmaz. Pratik çözüm, metni uygulamada normalize edip ayrı bir kolona yazmak ve aramayı o kolon üzerinde yapmaktır; böylece kullanıcı nasıl yazarsa yazsın sonuç döner.

    Üç harften kısa kelimeler neden bulunmuyor#

    Çünkü InnoDB varsayılan olarak üç karakterden kısa kelimeleri index'e almaz; bu davranış innodb_ft_min_token_size değişkeniyle kontrol edilir (MyISAM'da ft_min_word_len). Değeri düşürmek için yapılandırma dosyasını güncelleyip MySQL'i yeniden başlatman, ardından index'i yeniden oluşturman gerekir. Yalnızca ayarı değiştirmek yetmez, çünkü mevcut index eski kurallarla inşa edilmiştir.

    FULLTEXT index yazma performansını ne kadar etkiler#

    Normal bir index'ten belirgin biçimde daha maliyetlidir, çünkü her ekleme ve güncellemede metin kelimelere ayrılıp ters index güncellenir. InnoDB bu işi bir miktar tamponlayarak hafifletir ama yine de yoğun yazma alan tablolarda etki hissedilir. Bu yüzden çok sık güncellenen büyük metin tablolarında, aranabilir metni ayrı bir tabloya çıkarıp yazma yükünü ayırmak iyi bir stratejidir.

    Boolean modda sonuçlar neden alakaya göre sıralanmıyor#

    Çünkü boolean mod varsayılan olarak alaka puanına göre otomatik sıralama yapmaz; doğal dil modu yapar. Sıralama istiyorsan MATCH ... AGAINST ifadesini SELECT listesine de yazıp ORDER BY ile açıkça belirtmelisin. Aynı ifadeyi iki kez yazmak ek maliyet yaratmaz, optimizer bunu tek hesaplamaya indirger.

    Bölümlenmiş tabloya FULLTEXT index ekleyebilir miyim#

    Hayır, MySQL bölümlenmiş (partitioned) tablolarda FULLTEXT index'e izin vermez. Bu iki özelliği aynı tabloda kullanamazsın. Büyük bir arşiv tablosunda hem tarih bazlı bölümleme hem tam metin araması gerekiyorsa, aranabilir alanları ayrı ve bölümlenmemiş bir tabloda tutup ana tabloya kimlikle bağlamak uygulanabilir bir çözümdür.

    MySQL full-text araması ne zaman yetmez#

    Yazım hatası toleransı, kelime kökü analizi, eş anlamlı sözlük veya kırılımlı filtreleme gerektiğinde yetmez; bunların hiçbiri MySQL'de yoktur. Ayrıca milyonlarca belgede alt saniye yanıt ve arama yükünün ana veritabanından ayrılması gerekiyorsa ayrı bir arama motoru daha doğru olur. Bu ihtiyaçlar yoksa MySQL FULLTEXT, kurulum ve bakım maliyeti çok düşük olduğu için genellikle en akıllı seçimdir.

    Kapanış#

    MySQL full-text aramasından iyi sonuç almak birkaç temel alışkanlığa bağlı: MATCH kolon listesini index ile birebir aynı tut, boolean modda sıralamayı açıkça yaz, minimum kelime uzunluğu ve stopword ayarlarını kendi içeriğine göre düzenleyip index'i yeniden oluştur ve silme yoğun tablolarda düzenli OPTIMIZE TABLE çalıştır. Türkçe içerikte normalize edilmiş ayrı bir arama kolonu tutmak, kullanıcının nasıl yazdığından bağımsız sonuç üretmenin en pratik yoludur.

    Metin araması disk ve bellek açısından cömert bir iş yüküdür; index'in belleğe sığması yanıt sürelerini doğrudan belirler. Arama yapan bir siteyi hızlı NVMe disk ve garanti edilen kaynaklarla çalıştırmak istersen VDS ve bulut sunucu paketlerimize göz atabilirsin. İçerik odaklı siteler için WordPress hosting paketlerimiz hazır gelir; arama performansını da kapsayan bir iyileştirme isterseniz SEO ve sunucu yönetimi hizmetlerimiz devreye girebilir.

    MySQLAramaIndex

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.