MariaDB ve MySQL’de PRIMARY KEY ve AUTO_INCREMENT Kullanımı
Veritabanı tasarımında en temel ama aynı zamanda en kritik kararlardan biri, tablolarınızdaki her satırı benzersiz şekilde tanımlamak için doğru bir strateji belirlemektir. PRIMARY KEY ve AUTO_INCREMENT kavramları, bu noktada devreye giren ve neredeyse her MariaDB/MySQL projesinde karşılaşacağınız iki temel yapı taşıdır. Yeni bir veritabanı şeması tasarlıyor olun ya da mevcut bir sistemi optimize etmeye çalışıyor olun, bu iki özelliği doğru anlamak sizi pek çok baş ağrısından kurtaracaktır.
PRIMARY KEY Nedir ve Neden Önemlidir?
PRIMARY KEY, bir tablodaki her satırı benzersiz şekilde tanımlayan sütun ya da sütun grubudur. Bir tabloda yalnızca bir tane PRIMARY KEY tanımlanabilir ve bu anahtar hiçbir zaman NULL değer alamaz. Veritabanı motoru, PRIMARY KEY üzerinde otomatik olarak bir indeks oluşturur; bu da sorgu performansını doğrudan etkiler.
Düşünün ki bir e-ticaret platformu işletiyorsunuz. Siparişler tablosunda her siparişin kendine özgü bir kimliği olması gerekiyor. İki siparişin aynı ID’ye sahip olması, veri bütünlüğünü tamamen mahveder ve ödeme işlemlerinde felakete yol açabilir. İşte PRIMARY KEY tam burada devreye girer.
PRIMARY KEY’in sağladığı garantiler:
- Her satır benzersiz şekilde tanımlanır
- NULL değer kabul edilmez
- Otomatik olarak indekslenir, JOIN ve WHERE sorgularında hız sağlar
- Foreign key ilişkilerinin referans noktasını oluşturur
- InnoDB motorunda verilerin fiziksel sıralaması PRIMARY KEY’e göre yapılır
Basit bir tablo oluşturma örneğiyle başlayalım:
mysql -u root -p veritabani_adi << 'EOF'
CREATE TABLE kullanicilar (
id INT NOT NULL,
kullanici_adi VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
olusturma_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
EOF
Bu örnekte id sütununu PRIMARY KEY olarak tanımladık. Ancak bu haliyle her yeni kayıt eklediğinizde ID değerini manuel olarak vermeniz gerekir, bu da hem zahmetli hem de hata prone bir yaklaşımdır.
AUTO_INCREMENT ile Otomatik ID Yönetimi
AUTO_INCREMENT, yeni bir satır eklendiğinde ilgili sütunun değerini otomatik olarak artıran bir özelliktir. Genellikle PRIMARY KEY ile birlikte kullanılır ve sayısal ID sütunlarında hayat kurtarır.
mysql -u root -p veritabani_adi << 'EOF'
CREATE TABLE kullanicilar (
id INT NOT NULL AUTO_INCREMENT,
kullanici_adi VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
sifre_hash VARCHAR(255) NOT NULL,
aktif TINYINT(1) DEFAULT 1,
olusturma_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
EOF
Artık yeni kullanıcı eklerken ID değerini belirtmenize gerek yok:
mysql -u root -p veritabani_adi << 'EOF'
INSERT INTO kullanicilar (kullanici_adi, email, sifre_hash)
VALUES ('ahmet_yilmaz', '[email protected]', SHA2('gizli123', 256));
INSERT INTO kullanicilar (kullanici_adi, email, sifre_hash)
VALUES ('fatma_kaya', '[email protected]', SHA2('parola456', 256));
-- Eklenen kayitlarin ID degerlerini goruntule
SELECT id, kullanici_adi, email FROM kullanicilar;
EOF
MariaDB/MySQL, ilk kayda 1, ikinciye 2 değerini otomatik olarak atayacaktır. LAST_INSERT_ID() fonksiyonu ile de en son eklenen kaydın ID’sine ulaşabilirsiniz.
AUTO_INCREMENT Başlangıç Değeri ve Artış Miktarı
Bazen AUTO_INCREMENT’in 1’den başlamasını istemeyebilirsiniz. Örneğin eski bir sistemden veri taşıyorsunuz ve mevcut ID’lerin üzerine yazmak istemiyorsunuz. Ya da ID’lerin belirli bir aralıkta başlamasını zorunlu kılıyor.
mysql -u root -p veritabani_adi << 'EOF'
-- Tablo olusturulurken baslangic degeri belirle
CREATE TABLE siparisler (
siparis_id INT NOT NULL AUTO_INCREMENT,
kullanici_id INT NOT NULL,
toplam_tutar DECIMAL(10,2) NOT NULL,
siparis_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
durum ENUM('beklemede', 'onaylandi', 'kargoda', 'teslim_edildi', 'iptal') DEFAULT 'beklemede',
PRIMARY KEY (siparis_id)
) AUTO_INCREMENT = 10000;
-- Varolan bir tabloda AUTO_INCREMENT degerini degistir
ALTER TABLE siparisler AUTO_INCREMENT = 50000;
-- Mevcut AUTO_INCREMENT degerini sorgula
SELECT AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'veritabani_adi'
AND TABLE_NAME = 'siparisler';
EOF
Bu örnekte siparişler 10000’den başlayacak. Müşteriye “SİP-10001” gibi bir referans numarası vermek hem daha profesyonel görünür hem de gerçek kayıt sayısını gizler.
AUTO_INCREMENT ile ilgili bilinmesi gereken önemli noktalar:
- Silinen kayıtların ID’leri geri kullanılmaz, boşluk oluşur
- Sunucu yeniden başlatıldığında InnoDB, tablodaki MAX(id)+1 değerinden devam eder (MariaDB 10.2.4 öncesi)
- MariaDB 10.2.4 ve sonrasında AUTO_INCREMENT değeri redo log’a yazılır, bu daha güvenilirdir
BIGINT UNSIGNED AUTO_INCREMENTkullanarak teorik maksimum değeri 18.4 katrilyon’a çıkarabilirsiniz
Farklı PRIMARY KEY Stratejileri
Tek Sütunlu PRIMARY KEY
En yaygın kullanılan yaklaşımdır. Yukarıdaki örnekler bu kategoriye girer. Basit, anlaşılır ve yönetimi kolaydır.
mysql -u root -p veritabani_adi << 'EOF'
CREATE TABLE urunler (
urun_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
urun_adi VARCHAR(200) NOT NULL,
kategori_id INT UNSIGNED NOT NULL,
fiyat DECIMAL(10,2) NOT NULL,
stok_miktari INT DEFAULT 0,
barkod VARCHAR(50) UNIQUE,
PRIMARY KEY (urun_id)
);
EOF
Bileşik PRIMARY KEY (Composite Primary Key)
Bazen tek bir sütun yetmez; iki veya daha fazla sütunun kombinasyonu benzersizliği sağlar. Çoka-çok ilişkileri temsil eden ara tablolarda sıkça kullanılır.
mysql -u root -p veritabani_adi << 'EOF'
-- Kullanici-Rol iliskisini tutan ara tablo
CREATE TABLE kullanici_roller (
kullanici_id INT UNSIGNED NOT NULL,
rol_id INT UNSIGNED NOT NULL,
atama_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
atayan_kullanici_id INT UNSIGNED,
PRIMARY KEY (kullanici_id, rol_id)
);
-- Siparis urun detaylari
CREATE TABLE siparis_detay (
siparis_id INT UNSIGNED NOT NULL,
urun_id INT UNSIGNED NOT NULL,
miktar INT NOT NULL,
birim_fiyat DECIMAL(10,2) NOT NULL,
indirim_orani DECIMAL(5,2) DEFAULT 0.00,
PRIMARY KEY (siparis_id, urun_id)
);
EOF
Bileşik PRIMARY KEY kullanırken dikkat edilmesi gereken nokta, bu tablolara genellikle AUTO_INCREMENT uygulanamaz. AUTO_INCREMENT yalnızca PRIMARY KEY veya UNIQUE indeksin tek sütunlu olduğu durumlarda ya da bileşik indeksin ilk sütununda kullanılabilir.
UUID ile PRIMARY KEY
Yüksek trafikli, dağıtık sistemlerde veya mikroservis mimarilerinde UUID kullanmak popüler bir alternatiftir. MariaDB 10.7+ ile UUID için özel destek gelmiştir.
mysql -u root -p veritabani_adi << 'EOF'
-- Klasik UUID yaklasimi
CREATE TABLE oturumlar (
oturum_id CHAR(36) NOT NULL DEFAULT (UUID()),
kullanici_id INT UNSIGNED NOT NULL,
ip_adresi VARCHAR(45),
baslangic_zamani DATETIME DEFAULT CURRENT_TIMESTAMP,
son_aktivite DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
aktif TINYINT(1) DEFAULT 1,
PRIMARY KEY (oturum_id)
);
-- UUID ekleme ornegi
INSERT INTO oturumlar (kullanici_id, ip_adresi)
VALUES (42, '192.168.1.100');
SELECT oturum_id, kullanici_id, baslangic_zamani FROM oturumlar;
EOF
UUID kullanımının avantajları ve dezavantajları:
- Avantaj: Farklı sunucular veya uygulamalar arasında çakışma riski yok
- Avantaj: ID’ler tahmin edilemez, güvenlik açısından daha iyi
- Dezavantaj: INT’e göre çok daha fazla depolama alanı kaplar (36 byte vs 4 byte)
- Dezavantaj: InnoDB’de rastgele UUID’ler B-tree indeksinde fragmantasyona yol açar
- Dezavantaj: İnsan tarafından okunması ve yazılması zordur
Gerçek Dünya Senaryosu: Bir Blog Platformu Veritabanı
Küçük ama gerçekçi bir blog platformu için eksiksiz bir şema tasarlayalım. Bu senaryo pek çok sysadmin’in karşılaşabileceği tipik bir web uygulaması altyapısını temsil ediyor.
mysql -u root -p << 'EOF'
CREATE DATABASE IF NOT EXISTS blog_platform CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci;
USE blog_platform;
CREATE TABLE yazarlar (
yazar_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
kullanici_adi VARCHAR(50) NOT NULL,
email VARCHAR(150) NOT NULL,
tam_ad VARCHAR(100),
bio TEXT,
avatar_url VARCHAR(500),
kayit_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
son_giris DATETIME,
durum ENUM('aktif', 'pasif', 'yasakli') DEFAULT 'aktif',
PRIMARY KEY (yazar_id),
UNIQUE KEY uk_kullanici_adi (kullanici_adi),
UNIQUE KEY uk_email (email)
) AUTO_INCREMENT = 1;
CREATE TABLE kategoriler (
kategori_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
kategori_adi VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL,
ust_kategori_id SMALLINT UNSIGNED DEFAULT NULL,
aciklama TEXT,
PRIMARY KEY (kategori_id),
UNIQUE KEY uk_slug (slug)
);
CREATE TABLE yazilar (
yazi_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
yazar_id INT UNSIGNED NOT NULL,
kategori_id SMALLINT UNSIGNED,
baslik VARCHAR(300) NOT NULL,
slug VARCHAR(300) NOT NULL,
ozet TEXT,
icerik LONGTEXT NOT NULL,
kapak_resim VARCHAR(500),
yayin_tarihi DATETIME,
guncelleme_tarihi DATETIME ON UPDATE CURRENT_TIMESTAMP,
goruntulenme INT UNSIGNED DEFAULT 0,
yorum_sayisi INT UNSIGNED DEFAULT 0,
durum ENUM('taslak', 'yayinda', 'arsiv') DEFAULT 'taslak',
PRIMARY KEY (yazi_id),
UNIQUE KEY uk_slug (slug),
KEY idx_yazar (yazar_id),
KEY idx_kategori (kategori_id),
KEY idx_yayin_tarihi (yayin_tarihi)
) AUTO_INCREMENT = 1000;
CREATE TABLE yorumlar (
yorum_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
yazi_id INT UNSIGNED NOT NULL,
yazar_id INT UNSIGNED,
misafir_ad VARCHAR(100),
misafir_email VARCHAR(150),
icerik TEXT NOT NULL,
ust_yorum_id INT UNSIGNED DEFAULT NULL,
olusturma_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
durum ENUM('onay_bekliyor', 'onaylandi', 'spam') DEFAULT 'onay_bekliyor',
PRIMARY KEY (yorum_id),
KEY idx_yazi (yazi_id),
KEY idx_yazar (yazar_id)
);
-- Etiket - Yazi iliskisi (coka-cok, bilesik PK)
CREATE TABLE etiketler (
etiket_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
etiket_adi VARCHAR(50) NOT NULL,
slug VARCHAR(50) NOT NULL,
PRIMARY KEY (etiket_id),
UNIQUE KEY uk_slug (slug)
);
CREATE TABLE yazi_etiketler (
yazi_id INT UNSIGNED NOT NULL,
etiket_id SMALLINT UNSIGNED NOT NULL,
ekleme_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (yazi_id, etiket_id)
);
EOF
Dikkat edin: yazarlar tablosu 1’den, yazilar tablosu 1000’den başlıyor. Bu bilinçli bir tercih; yeni kurulan bir blogda 1. yazının olması doğal görünürken, ID’nin 1000’den başlaması sistemi daha köklü gösterebilir ve analitik araçlarda bazı minimum değer kontrollerini atlatır.
AUTO_INCREMENT Değerini Sıfırlama ve Yönetme
Üretim ortamında zaman zaman AUTO_INCREMENT değerini yönetmeniz gerekebilir. Test verisi temizleme, migration işlemleri veya tablo yeniden yapılandırma gibi senaryolarda bu bilgi işinize yarar.
mysql -u root -p veritabani_adi << 'EOF'
-- Tablodaki tum verileri sil ve AUTO_INCREMENT'i sifirla
TRUNCATE TABLE test_verileri;
-- Sadece AUTO_INCREMENT degerini belirli bir noktaya ayarla
-- (Bu komut mevcut max degerden kucuk bir deger verirseniz etkisiz olur)
ALTER TABLE kullanicilar AUTO_INCREMENT = 1;
-- Mevcut max ID'yi ogrenip bir sonraki degeri hesapla
SELECT MAX(id) + 1 AS sonraki_id FROM kullanicilar;
-- Guvenli AUTO_INCREMENT guncelleme
SET @mevcut_max = (SELECT IFNULL(MAX(id), 0) FROM kullanicilar);
SET @yeni_deger = @mevcut_max + 1;
SET @sorgu = CONCAT('ALTER TABLE kullanicilar AUTO_INCREMENT = ', @yeni_deger);
PREPARE stmt FROM @sorgu;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
EOF
PRIMARY KEY Değişikliği ve Migration Senaryosu
Mevcut bir üretim tablosunda PRIMARY KEY değişikliği yapmak riskli ve dikkat gerektiren bir işlemdir. Büyük tablolarda bu işlem saatler sürebilir ve servis kesintisine yol açabilir.
mysql -u root -p veritabani_adi << 'EOF'
-- Oncelikle tabloyu yedekle
CREATE TABLE eski_urunler_yedek AS SELECT * FROM urunler;
-- Mevcut PRIMARY KEY'i kaldir ve yenisini ekle
-- (Bu islem buyuk tablolarda uzun surer, dikkatli ol)
ALTER TABLE urunler
DROP PRIMARY KEY,
ADD COLUMN yeni_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD PRIMARY KEY (yeni_id);
-- Eski id sutununu yeniden adlandir
ALTER TABLE urunler
CHANGE eski_urun_id urun_kodu INT NOT NULL;
-- Islem sonrasi veri butunlugunu kontrol et
SELECT COUNT(*) FROM urunler;
SELECT COUNT(*) FROM eski_urunler_yedek;
SELECT MIN(yeni_id), MAX(yeni_id) FROM urunler;
EOF
Üretim ortamında PRIMARY KEY değişikliği için öneriler:
- Önce bir test ortamında deneyin ve süreyi ölçün
- Yoğun olmayan saatlerde (gece maintenance window) yapın
pt-online-schema-changeveyagh-ostaraçlarını tercih edin, bunlar tabloyu kilitlemeden çalışır- Her zaman tam yedek alın
- Foreign key ilişkilerini de güncellemeyi unutmayın
- Değişiklik öncesi ve sonrası kayıt sayılarını karşılaştırın
Sık Yapılan Hatalar ve Çözümleri
Yıllar içinde pek çok veritabanı sorunuyla karşılaştım. En sık yapılan hatalar şunlar:
AUTO_INCREMENT sütununu NULL yapılabilir yapmak: id INT AUTO_INCREMENT yerine id INT NOT NULL AUTO_INCREMENT yazın. NOT NULL olmadan bazı edge case’lerde beklenmedik davranışlar görebilirsiniz.
BIGINT yerine INT kullanıp overflow’a gitmek: INT maksimum 2,147,483,647 değerini tutabilir. Hızlı büyüyen sistemlerde bu değere ulaşmak düşündüğünüzden çabuk olur. Loglar, event’ler veya işlem kayıtları tutan tablolarda baştan BIGINT UNSIGNED AUTO_INCREMENT kullanın.
Silinen kayıtların ID’sinin geri geleceğini sanmak: 1, 2, 3, 4, 5 ID’niz varsa ve 3’ü silerseniz bir sonraki kayıt 6 alır, 3 değil. Bu tasarım gereğidir. Eğer uygulama mantığınız ID sürekliliğine bağımlıysa cidli bir tasarım probleminiz var demektir.
Her tabloya UUID primary key vermek: UUID’nin gerçekten gerekli olduğu durumlarda kullanın. Sadece internal sistemlerde INT AUTO_INCREMENT çok daha performanslı ve yönetilebilirdir.
mysql -u root -p veritabani_adi << 'EOF'
-- Yanlis: Tasima sonrasi AUTO_INCREMENT guncellemeyi unutmak
-- Simdi dogru yapiyoruz:
INSERT INTO kullanicilar SELECT * FROM eski_kullanicilar;
-- En yuksek ID'yi bul
SELECT MAX(id) FROM kullanicilar;
-- AUTO_INCREMENT'i guncel deger + 1 olarak ayarla
ALTER TABLE kullanicilar AUTO_INCREMENT = (SELECT MAX(id) + 1 FROM kullanicilar);
-- Kontrol et
SHOW CREATE TABLE kullanicilarG
EOF
Performans ve İzleme
PRIMARY KEY ve AUTO_INCREMENT ile ilgili performans sorunlarını erken tespit etmek için birkaç sorgu:
mysql -u root -p veritabani_adi << 'EOF'
-- Tablolarin AUTO_INCREMENT doluluk oranini kontrol et
-- (INT icin max 2147483647, BIGINT icin cok daha fazla)
SELECT
TABLE_NAME,
AUTO_INCREMENT,
2147483647 AS int_maks,
ROUND((AUTO_INCREMENT / 2147483647) * 100, 2) AS doluluk_yuzdesi
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'veritabani_adi'
AND AUTO_INCREMENT IS NOT NULL
ORDER BY doluluk_yuzdesi DESC;
-- PRIMARY KEY olmayan tablolari bul
SELECT
TABLE_NAME,
TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'veritabani_adi'
AND TABLE_NAME NOT IN (
SELECT TABLE_NAME
FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = 'veritabani_adi'
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
);
EOF
Bu sorgular rutin veritabanı bakımınızın bir parçası olmalı. Özellikle INT ile tanımladığınız AUTO_INCREMENT sütununun doluluk oranı %80’i geçiyorsa alarm vermeniz gerekir.
Sonuç
PRIMARY KEY ve AUTO_INCREMENT, veritabanı tasarımının ABC’sidir ama yüzeysel bilgiyle geçiştirilen konuların başında gelir. Doğru veri tipi seçimi (INT mi, BIGINT mi), doğru başlangıç değeri, bileşik anahtar gereksinimlerinin tespiti ve migration senaryolarında dikkat edilmesi gereken noktalar; bunların hepsi gerçek üretim ortamında kritik fark yaratır.
Özetlemek gerekirse:
- Her zaman PRIMARY KEY tanımlayın, PRIMARY KEY olmayan tablolar performans ve veri bütünlüğü açısından risk taşır
- Hızlı büyüyecek tablolarda baştan
BIGINT UNSIGNEDkullanın, sonradan değiştirmek çok maliyetlidir - AUTO_INCREMENT doluluk oranını düzenli izleyin
- Çoka-çok ilişki tablolarında bileşik PRIMARY KEY kullanmayı değerlendirin
- Migration işlemlerinde AUTO_INCREMENT değerini sıfırlamayı veya güncellemeyi unutmayın
- Dağıtık sistemler için UUID iyi bir alternatiftir ama beraberinde getirdiği maliyeti göze alın
Veritabanı tasarımında atılan sağlam adımlar, ilerleyen dönemlerde onlarca saat kazandırır. Bugün birkaç dakika ekstra düşünmek, yarın gece 2’de production veritabanında yangın söndürmeyi önleyebilir.
