İçeriğe geç

Tam Metin Arama — LIKE '%kira%' Neden Hem Yavaş Hem Eksik?

Orta 9 dk Sık karşılaşılır

Önce şunu oku: SQL İndeksleme ve EXPLAIN

30 saniyede özet

Metinde kelime aramak için LIKE '%...%' bütün tabloyu okur ve büyük harf, ek ya da Türkçe İ yüzünden sonuç kaçırır. Ters indeks her kelimeyi geçtiği satırlara bağlar; dili bilen bir analizle hem hızlı hem doğru arar.

Kalın bir kitapta bir kelimeyi arıyorsun. Her sayfayı tek tek okuyabilirsin, ya da kitabın sonundaki dizine bakıp kelimenin geçtiği sayfalara doğrudan gidebilirsin. Ama dizini hazırlayan kişi “Kira” ile “kirası”nı ayrı kelimeler saydıysa, aradığın sayfaların çoğunu kaçırırsın.

  1. Bayt: Hareketlerde arama ekledik: WHERE description LIKE '%kira%'. Basit ve çalışıyor!

  2. Sen: Müşteri altı kira ödemesinden yalnızca dördünü gördü. Bir de araç kiralamayı gösterdi.

  3. Bayt: Bir de sorgu milyon satırlık tabloda saniyeler sürüyor...

  4. Bayt: Biz harf arıyoruz, müşteri kelime arıyor. Kitabın dizinine bakalım.

LIKE neden yetmez?

LIKE '%kira%' metnin herhangi bir yerinde bu harf dizisini arar. Baştaki joker karakter yüzünden B-tree indeks kullanılamaz; veritabanı her satırı tek tek okur.

Üstelik harf dizisi arar, kelime değil. “Araç kiralama”yı bulur, çünkü harfler içinde geçer; “KİRA ödemesi”ni ise büyük harf yüzünden kaçırır.

Kafam karıştı, daha basit anlat

LIKE ‘%kira%’ her satırı okur ve kelime değil harf arar.

Hızlı kontrolBaşlangıç

WHERE description LIKE '%kira%' neden büyük bir tabloda yavaştır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

LIKE '%kira%' sorgusu 'Araç kiralama' satırını neden bulur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kelimeden satıra

Ters indeks kurduk ama metinleri yalnızca varsayılan kurallarla küçük harfe çevirdik. 'KİRA ödemesi' satırı 'kira' aramasında bulunur mu? Cevabı göster

Bulunmaz. Varsayılan küçük harf kuralı Türkçe “İ” harfini “i” yerine “i ve üstünde bir nokta” olarak çevirir; kelime “kira” ile eşleşmez. Üstelik “kirası” da ayrı bir kelime sayılır.

Doğru arama, dili bilen bir dizinle yapılır.
Adım adım oku
  1. Her sayfayı tek tek okumak: yavaş.
  2. Kitabın sonundaki dizin: kelime → sayfalar.
  3. Dizin dili bilmezse: 'Kira', 'kirası' ayrı kelime.
  4. Doğru arama, dili bilen bir dizinle yapılır.

Bir ters indeksHer kelimeyi geçtiği satırların listesine bağlayan yapı. Tam metin araması tabloyu değil, yalnızca aranan kelimenin listesini okur.Sözlükte gör →, her kelimeyi geçtiği satırların listesine bağlar. Arama, tabloyu değil yalnızca aranan kelimenin listesini okur; bu yüzden tablo büyüdükçe bile hızlı kalır.

Ama ters indeks, metni kelimelere ayıran kurallar kadar iyidir. Türkçe küçük harf (I→ı, İ→i) ve ekleri atan bir kök bulmaBir kelimeyi eklerinden arındırıp köküne indirmek: 'kirası' ve 'kirasi' 'kira' olur. Ekli dillerde aramanın doğruluğu buna bağlıdır.Sözlükte gör → adımı, “KİRA”, “Kira” ve “kirası”nı aynı kelimeye indirir.

Kafam karıştı, daha basit anlat

Ters indeks kelimeden satıra gider. Dilin kurallarını bilirse her yazımı aynı kelimede toplar.

Hızlı kontrolOrta

Ters indeks (inverted index) nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kök bulma (stemming) arama için neden önemlidir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Hareketlerde "kira" ara

Tohum 524053

Arama: kira

  • Ekim kirası
  • KİRA ödemesi
  • Kira depozitosu
  • Market alışverişi
  • Elektrik faturası
  • Araç kiralama
  • kira artışı farkı
  • Okul taksidi
  • İş yeri kirası
  • Kırtasiye
  • Su faturası
  • Ev kirasi

Oynat ya da adımla: önce satırlar okunur, sonra sonuç gelir.

Hız
Adım 0

Şu an ne oldu?

LIKE '%kira%' (bütün tabloyu tara)

12 hareketin 6’sı gerçekten kira ödemesi.

Görevler0/3

  • Bütün tabloyu oku ve yine de kira kaçıraçık

    İpucu

    Varsayılan ayarlar yeter.

  • "KİRA" yazan hareketi Türkçe büyük İ yüzünden kaçıraçık

    İpucu

    Dili bilmeyen bir indeks.

  • Altı kira ödemesini de bul, başka hiçbir şey bulmaaçık

    İpucu

    Türkçeyi bilen bir analiz.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. LIKE ‘%kira%’: bütün tablo okundu, iki kira kaçtı, araç kiralama yanlışlıkla eşleşti.
  2. “LIKE ‘kira%’ + B-tree” seç. Hızlı, ama yalnızca “kira” ile başlayan tek hareket bulundu.
  3. “Ters indeks, varsayılan küçük harf” seç. Yalnızca iki sonuç: “KİRA” ve “kirası” kaçtı.
  4. “Ters indeks, Türkçe analiz” seç. Altı kira ödemesinin hepsi bulundu, yanlış eşleşme yok.
Hızlı kontrolOrta

Ters indeks var ama metinler varsayılan kurallarla küçük harfe çevrilmiş. 'KİRA ödemesi' neden bulunamaz?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

LIKE 'kira%' sorgusu indeks kullanabilir ama ne kaçırır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Tuzaklar

Varsayılan dil ayarıyla yetinmek: İngilizce kurallar Türkçe İ’yi ve ekleri tanımaz. Arama yapılandırmasını Türkçe seç ve gerçek verilerle dene.

Her şeyi tek bir sorguda aramak. IBAN ya da müşteri numarası gibi kodlar kelime değildir; onları ayrı bir alanda, tam eşleşmeyle ara. Tam metin araması serbest yazı içindir.

İndeksi güncel tutmayı unutmak. Ayrı bir arama sunucusu kullanıyorsan, veritabanındaki değişikliğin oraya da ulaştığından emin ol. Aksi hâlde yeni hareketler aramada görünmez.

Kafam karıştı, daha basit anlat

Türkçe yapılandır, kodları ayrı ara, arama indeksini güncel tut.

Hızlı kontrolOrta

Müşteri arama kutusuna bir IBAN yazdı. Nasıl aranmalı?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
Derinleş · Hareket araması: PostgreSQL'de Türkçe tam metin 4 dosya · ~58 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/resources/db/migration/ V34__transaction_search.sql Migration: Türkçe analizle üretilen kelime vektörü ayrı bir sütunda tutuluyor ve GIN indeksiyle aranıyor.

src/main/resources/db/migration/V34__transaction_search.sql
-- A word vector built with PostgreSQL's Turkish configuration, kept up to date by the database.
ALTER TABLE account_transaction
ADD COLUMN description_search tsvector
GENERATED ALWAYS AS (to_tsvector('turkish', coalesce(description, ''))) STORED;
CREATE INDEX ix_tx_description_search ON account_transaction USING GIN (description_search);

src/main/java/com/bank/history/ TransactionSearchRepository.java Sorgu: arama metni de aynı Türkçe kurallarla kelimelere çevriliyor; tablo değil indeks okunuyor.

src/main/java/com/bank/history/TransactionSearchRepository.java
interface TransactionSearchRepository extends Repository<AccountTransaction, Long> {
// The query text goes through the same Turkish rules as the stored descriptions.
@Query(value = """
SELECT * FROM account_transaction
WHERE account_id = :accountId
AND description_search @@ plainto_tsquery('turkish', :text)
ORDER BY created_at DESC
LIMIT 50
""", nativeQuery = true)
List<AccountTransaction> searchDescriptions(String accountId, String text);
List<AccountTransaction> findByAccountIdAndCounterpartyIban(String accountId, String iban);
}

src/main/java/com/bank/history/ TransactionSearch.java Servis: IBAN gibi kodlar ayrı bir alanda tam eşleşmeyle aranıyor; serbest metin tam metin aramasına gidiyor.

src/main/java/com/bank/history/TransactionSearch.java
@Service
class TransactionSearch {
private static final Pattern IBAN = Pattern.compile("^TR\\d{24}$");
private final TransactionSearchRepository transactions;
TransactionSearch(TransactionSearchRepository transactions) {
this.transactions = transactions;
}
List<AccountTransaction> search(String accountId, String input) {
String compact = input.replace(" ", "").toUpperCase(Locale.ROOT);
if (IBAN.matcher(compact).matches()) {
return transactions.findByAccountIdAndCounterpartyIban(accountId, compact); // a code: exact match
}
return transactions.searchDescriptions(accountId, input); // free text: full-text search
}
}

src/test/java/com/bank/history/ TransactionSearchIT.java Test: KİRA, Kira ve kirası aynı sonucu veriyor, araç kiralama vermiyor; gerçek bir PostgreSQL ile.

src/test/java/com/bank/history/TransactionSearchIT.java
@DataJpaTest
@Testcontainers
class TransactionSearchIT {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired TransactionSearchRepository search;
@Autowired TestEntityManager db;
@Test
void findsEverySpellingOfRentButNotCarRental() {
for (String text : List.of("KİRA ödemesi", "Kira depozitosu", "Ekim kirası", "Araç kiralama")) {
db.persist(AccountTransaction.of("TR01", text));
}
assertThat(search.searchDescriptions("TR01", "kira")).extracting(AccountTransaction::getDescription)
.containsExactlyInAnyOrder("KİRA ödemesi", "Kira depozitosu", "Ekim kirası");
}
}

Kendini sına

Şimşek turu1/4

LIKE '%kira%' sorgusu bir B-tree indeksini kullanarak hızlıca çalışır.

Soru 1/3İleri

Arama ayrı bir arama sunucusunda (örneğin Elasticsearch) tutuluyor. Yeni hareketler aramada görünmüyor. Olası sebep nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aklında kalacak üç şey

  1. 1 Baştaki joker karakterli LIKE ('%kira%') B-tree indeksi kullanamaz ve bütün tabloyu okur. Üstelik harf dizisi arar: 'kiralama'yı bulur, 'KİRA'yı kaçırır.
  2. 2 Ters indeks her kelimeyi geçtiği satırlara bağlar; arama, tabloyu değil yalnızca o kelimenin listesini okur.
  3. 3 Ters indeks, metni kelimelere ayıran kurallar kadar iyidir. Türkçe küçük harf ve ek temizleme olmadan 'KİRA' ve 'kirası' ayrı kelimeler sayılır.
Sonraki kapı Saniyede binlerce kart işlemi yazan sistem neden B-tree kullanan klasik bir veritabanı yerine Cassandra seçti? LSM Ağacı — Yazmayı Ekleyerek Hızlanan Veritabanları · 10 dk

4 kart sonraki derste seni bekliyor

0/4 kart bu dersten toplandı