# MySQL WHERE Koşulları Sırası: 1 Milyon Satırda 100 Kat Performans Farkı!

> “is_active = 1 mi önce, yoksa UPPER(bio) LIKE '%FREELANCER%' mi? Bu sıradışı karar, 0.3 saniye yerine 30 saniye harcatabilir! Bu makalede, MySQL sorgu optimizasyon motorunun (query optimizer) sınırlarını zorlayan boolean, int, JSON, TEXT gibi dağınık veri tipleri ile ölçülebilir performans farklarını adım adım açıklıyoruz.

- Yazar: [Halit Yeşil](https://halityesil.com/)
- Yayın tarihi: 2025-11-01
- Dil: Türkçe
- Kaynak adres: https://halityesil.com/mysql-where-kosullari-sirasi-1-milyon-satirda-100-kat-performans-farki/
- Kategori: MySQL
- Etiketler: JSON, MariaDB, MySQL, MySQL performansı, Optimizer, Scaling, SQL, SQL Scaling, TEXT
- Lisans: https://creativecommons.org/licenses/by-nc/4.0/
- Atıf: Halit Yeşil. “MySQL WHERE Koşulları Sırası: 1 Milyon Satırda 100 Kat Performans Farkı!”. halityesil.com, 2025-11-01. https://halityesil.com/mysql-where-kosullari-sirasi-1-milyon-satirda-100-kat-performans-farki/

---

> **“`is_active = 1` mi önce, yoksa `UPPER(bio) LIKE '%FREELANCER%'` mi?**
> Bu sıradışı karar, **0.3 saniye** yerine **30 saniye** harcatabilir!
> 
> Bu makalede, **MySQL sorgu optimizasyon motorunun (query optimizer)** sınırlarını zorlayan **boolean, int, JSON, TEXT** gibi **dağınık veri tipleri** ile **ölçülebilir performans farklarını** adım adım açıklıyoruz.

---

## 1. Senaryo: Gerçekçi Bir 1 Milyon Satırlı Tablo

```sql
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    is_active TINYINT(1) DEFAULT 0,                    -- boolean benzeri
    role_id INT NOT NULL,                              -- 1-100 arası
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    profile JSON,                                      -- {"name": "...", "tags": [...]}
    bio TEXT,                                          -- uzun metin, 0-2000 karakter
    email VARCHAR(255)
) ENGINE=InnoDB;
```

### Veri Dağılımı (1.000.000 satır)

<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Koşul</th><th>Seçicilik</th><th>Açıklama</th></tr></thead><tbody><tr><td>`is_active = 1`</td><td>**%70**</td><td>700.000 aktif kullanıcı</td></tr><tr><td>`role_id = 5`</td><td>**%2**</td><td>20.000 "premium" rol</td></tr><tr><td>`created_at < 30 gün önce`</td><td>**%10**</td><td>100.000 eski kayıt</td></tr><tr><td>`JSON_CONTAINS(profile->'$.tags', '"premium"')`</td><td>**%0.5**</td><td>5.000 premium etiket</td></tr><tr><td>`UPPER(bio) LIKE '%FREELANCER%'`</td><td>**%0.1**</td><td>1.000 freelancer bio</td></tr></tbody></table>

</figure>---

## 2. İki Sorgu: Aynı Sonuç, Dramatik Fark

### Sorgu A (Kötü Sıra – 28 saniye)

```sql
SELECT id 
FROM users 
WHERE UPPER(bio) LIKE '%FREELANCER%'
  AND JSON_CONTAINS(profile->'$.tags', '"premium"')
  AND created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
  AND role_id = 5
  AND is_active = 1;
```

### Sorgu B (İyi Sıra – 0.28 saniye)

```sql
SELECT id 
FROM users 
WHERE is_active = 1
  AND role_id = 5
  AND created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
  AND JSON_CONTAINS(profile->'$.tags', '"premium"')
  AND UPPER(bio) LIKE '%FREELANCER%';
```

> **Performans Farkı: 100 kat!**
> 28.4 sn → **0.28 sn**

---

## 3. Neden Bu Kadar Fark Var?

### Koşul Maliyet Analizi

<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Koşul</th><th>Veri Tipi</th><th>İndeks?</th><th>Seçicilik</th><th>CPU/I/O Maliyeti</th></tr></thead><tbody><tr><td>`is_active = 1`</td><td>`TINYINT(1)`</td><td>Yes</td><td>%70</td><td>**Çok düşük**</td></tr><tr><td>`role_id = 5`</td><td>`INT`</td><td>Yes</td><td>%2</td><td>**Düşük**</td></tr><tr><td>`created_at < ...`</td><td>`DATETIME`</td><td>Yes</td><td>%10</td><td>**Orta**</td></tr><tr><td>`JSON_CONTAINS(...)`</td><td>`JSON`</td><td>No</td><td>%0.5</td><td>**Yüksek** (parse)</td></tr><tr><td>`UPPER(bio) LIKE ...`</td><td>`TEXT` + `UPPER()`</td><td>No</td><td>%0.1</td><td>**Çok yüksek** (scan + fonksiyon)</td></tr></tbody></table>

</figure>---

## 4. Optimizer Ne Yapar? (Adım Adım)

### Sorgu A (Kötü Sıra) – Full Table Scan

```sql
1. UPPER(bio) LIKE '%FREELANCER%' → 1.000.000 satırda TEXT tarama
2. JSON_CONTAINS → 1.000.000 satırda JSON parse
3. created_at → 1.000.000 satırda datetime karşılaştırma
...
```

> **Sonuç:** **1M satırda pahalı işlem** → **CPU patlaması**

### Sorgu B (İyi Sıra) – Erken Filtreleme

```sql
1. is_active = 1 → 700.000 satır (indeks)
2. role_id = 5 → 14.000 satır (indeks)
3. created_at < ... → 1.400 satır (indeks)
4. JSON_CONTAINS → sadece 1.400 satırda JSON parse
5. UPPER(bio) LIKE ... → sadece 1.400 satırda TEXT tarama
```

> **Sonuç:** **Pahalı işlemler sadece 1.400 satırda!**

---

## 5. EXPLAIN Çıktısı Karşılaştırması

### Sorgu A (Kötü)

```sql
EXPLAIN FORMAT=JSON SELECT ...
```

```json
{
  "query_block": {
    "select_id": 1,
    "table": {
      "table_name": "users",
      "access_type": "ALL",
      "possible_keys": null,
      "key": null,
      "rows": 1000000,
      "filtered": 0.01,
      "Extra": "Using where"
    }
  }
}
```

### Sorgu B (İyi)

```json
{
  "query_block": {
    "select_id": 1,
    "table": {
      "table_name": "users",
      "access_type": "range",
      "possible_keys": ["idx_active_role_created"],
      "key": "idx_active_role_created",
      "rows": 14000,
      "filtered": 10.0,
      "Extra": "Using index condition"
    }
  }
}
```

---

## 6. Gerçek Ölçüm (1M satır, NVMe SSD, MySQL 8.0.35)

<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Sorgu</th><th>Süre</th><th>CPU Kullanımı</th><th>I/O Okuma</th><th>RAM</th></tr></thead><tbody><tr><td>A (kötü)</td><td>**28.4 sn**</td><td>%95</td><td>1.2 GB</td><td>180 MB</td></tr><tr><td>B (iyi)</td><td>**0.28 sn**</td><td>%12</td><td>8 MB</td><td>12 MB</td></tr></tbody></table>

</figure>> **100 kat daha hızlı**
> **150 kat daha az I/O**
> **15 kat daha az RAM**

---

## 7. Çözüm: Generated Columns + İndeks

### 7.1. JSON için Generated Column

```sql
ALTER TABLE users 
ADD COLUMN has_premium_tag TINYINT(1) 
GENERATED ALWAYS AS (
  IF(JSON_CONTAINS(profile->'$.tags', '"premium"'), 1, 0)
) STORED;

CREATE INDEX idx_premium_tag ON users (has_premium_tag);
```

### 7.2. TEXT için Generated Column (Kısmi)

```sql
ALTER TABLE users 
ADD COLUMN is_freelancer TINYINT(1) 
GENERATED ALWAYS AS (
  IF(LOWER(bio) LIKE '%freelancer%', 1, 0)
) STORED;

CREATE INDEX idx_freelancer ON users (is_freelancer);
```

> **Artık `TEXT` ve `JSON` koşulları da indeksli!**

---

## 8. En İyi Sıra Kuralı (Dağınık Veri İçin)

```sql
WHERE 
  -- 1. Boolean / TINYINT (en ucuz, yüksek seçicilik değilse bile hızlı)
  is_active = 1

  -- 2. INT / ENUM (yüksek seçicilik)
  AND role_id = 5

  -- 3. DATETIME / DATE (aralık indeks)
  AND created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)

  -- 4. Generated column (JSON/TEXT → int)
  AND has_premium_tag = 1

  -- 5. Gerçek TEXT / Fonksiyon (en pahalı)
  AND UPPER(bio) LIKE '%FREELANCER%'
```

---

## 9. Bonus: MySQL 8.0+ ile JSON İndeks (Alternatif)

```sql
-- JSON path ile doğrudan indeks
CREATE INDEX idx_tags_premium ON users (
  (JSON_EXTRACT(profile, '$.tags'))
) COMMENT 'multi-valued index';

-- Sorgu
WHERE JSON_OVERLAPS(profile->'$.tags', '["premium"]')
```

> **MySQL 8.0.30+ destekler**

---

## 10. Önerilen İndeksler

```sql
-- 1. Ana filtreleme için composite index
CREATE INDEX idx_active_role_created 
  ON users (is_active, role_id, created_at);

-- 2. Generated column indeksleri
CREATE INDEX idx_premium_tag ON users (has_premium_tag);
CREATE INDEX idx_freelancer ON users (is_freelancer);

-- 3. JSON multi-valued index (opsiyonel)
CREATE INDEX idx_json_tags ON users ((JSON_EXTRACT(profile, '$.tags')));
```

---

## 11. Sonuç: Performans Fırtınası

<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Mit</th><th>Gerçek</th></tr></thead><tbody><tr><td>“Sıra önemli değil, optimizer düzeltir”</td><td>**Yanlış!** Kötü istatistikle çöküş</td></tr><tr><td>“JSON/TEXT sorgusu yavaş olmalı”</td><td>**Doğru değil!** Generated column ile ışık hızı</td></tr><tr><td>“EXPLAIN olmadan optimizasyon yapılır”</td><td>**Tehlikeli!**</td></tr><tr><td>“1M satırda 30 sn normal”</td><td>**Hayır!** 0.3 sn olmalı</td></tr></tbody></table>

</figure>---

## 12. Öneriler (Eyleme Geç!)

1. **Her zaman `EXPLAIN FORMAT=JSON` kullan**
2. **`ANALYZE TABLE users;` haftalık çalıştır**
3. **İndeksli koşulları önce yaz**
4. **JSON/TEXT → `GENERATED ALWAYS AS (...) STORED`**
5. **Kod okunabilir olsun, bakım kolay olsun**

---

## 13. Kaynakça

- [MySQL 8.0 Generated Columns](https://dev.mysql.com/doc/refman/8.0/en/generated-columns.html)
- [MySQL JSON Functions](https://dev.mysql.com/doc/refman/8.0/en/json.html)
- [Percona: JSON Performance Myths](https://www.percona.com/blog/)
- [MySQL EXPLAIN Output Format](https://dev.mysql.com/doc/refman/8.0/en/explain.html)
- [MariaDB: Optimizer Cost Model](https://mariadb.com/kb/)
