İçeriğe geç
Educora
Üniversite30 dk21 / 22

Uygulamada PostgreSQL ve MySQL

Sunucu VTYS'lerine geçiş: veri tipleri, IDENTITY ve AUTO_INCREMENT, JSON sütunları, SQLite'tan farklar, kullanıcılar ve yetkiler, yedekler.

Kendini test et
Bu derste öğreneceklerin
  • PostgreSQL ve MySQL'de doğru tipleri seçmek (özellikle para, tarih ve JSON için) ve otomatik anahtarlar oluşturmak
  • SQLite ile sunucu VTYS'leri arasındaki temel farkları açıklamak
  • En az yetki ilkesine göre roller ve izinler kurmak, yedekleme ve geri yükleme planlamak

Bu kurstaki tüm örnekler SQLite'ta çalışır: tek bir dosyada yaşayan ve telefonlarda, tarayıcılarda, küçük programlarda vazgeçilmez olan bir kütüphane. Bir web sitesinin ya da mobil uygulamanın arkasında ise genellikle bir sunucu VTYS'si durur; çoğunlukla PostgreSQL ya da MySQL. Bunlar ağ üzerinden binlerce bağlantıya hizmet eder; katı tipler, kullanıcılar ve izinler, yedekleme araçları sunar. SQL bilginin yaklaşık %90'ı aynı kalır, ama geri kalan %10 canlı ortamda ciddi hataların kaynağıdır. Bu ders o farklara ayrılmıştır.

SQLite'tan temel farklar

ÖzellikSQLitePostgreSQLMySQL (InnoDB)
Mimariuygulamanın içindeki kütüphane, tek dosyaistemci–sunucuistemci–sunucu
Tipleresnek (STRICT tablolar isteğe bağlı)katıkatı (sql_mode ayarına bağlı)
Eş zamanlı yazmaaynı anda tek yazıcıMVCC, çok sayıda yazıcıMVCC, çok sayıda yazıcı
Kullanıcılaryok — dosya izinleriroller, GRANT'user'@'host', GRANT
Otomatik idINTEGER PRIMARY KEYGENERATED … AS IDENTITYAUTO_INCREMENT
Mantıksal tip0 / 1 tam sayıBOOLEANTINYINT(1)

Hangisini seçmeli? İkisi de ücretsiz ve açık kaynaklıdır, ikisi de yıllardır büyük projelerde sınanmıştır. PostgreSQL zengin tipleri (JSONB, diziler, aralıklar), güçlü eklentileri (örneğin coğrafi veriler için PostGIS) ve SQL standardına yakınlığıyla öne çıkar; bu yüzden analitik ve karmaşık iş mantığı için sık seçilir. MySQL web barındırma hizmetlerinde çok yaygındır; kurulumu ve çoğaltması (replikasyon) basittir. Uygulamada seçim çoğu zaman ekibin deneyimine ve altyapıya bağlıdır; bu dersteki bilgiler ise ikisi için de gereklidir.

SQL
-- SQLite: an ordinary table accepts almost anything
CREATE TABLE loose (n INTEGER);
INSERT INTO loose VALUES ('abc');

-- a STRICT table (SQLite 3.37+) behaves like PostgreSQL
CREATE TABLE strict_t (n INTEGER) STRICT;
INSERT INTO strict_t VALUES ('abc');
▸ Beklenen çıktı
Error: cannot store TEXT value in INTEGER column strict_t.n
Sıradan tablo 'abc' metnini tam sayı sütununa sessizce yazar; PostgreSQL ve STRICT tablo ise hata verir.

Veri tipleri ve otomatik anahtarlar

Bir sunucu VTYS'sinde tip hem depolamayı hem de doğruluğu etkiler. Para için her zaman kesin bir ondalık tip kullan: PostgreSQL'de NUMERIC(10, 2), MySQL'de DECIMAL(10, 2). Zaman için PostgreSQL'de saat dilimini dikkate alan TIMESTAMPTZ tipini seç. Otomatik anahtarı PostgreSQL 10'dan beri kullanılabilen standart yazımla oluştur: GENERATED ALWAYS AS IDENTITY; eski SERIAL hâlâ çalışır ama yeni projelerde önerilmez. MySQL'de AUTO_INCREMENT kullanılır.

SQL
-- PostgreSQL
CREATE TABLE products (
  id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name       VARCHAR(100)   NOT NULL,
  category   VARCHAR(50),
  price      NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
  stock      INTEGER        NOT NULL DEFAULT 0,
  attributes JSONB,
  created_at TIMESTAMPTZ    NOT NULL DEFAULT now()
);

INSERT INTO products (name, category, price, attributes)
VALUES ('Laptop', 'Electronics', 1450.00, '{"ram_gb": 16, "color": "silver"}')
RETURNING id, created_at;
Beklenen çıktı
 id |          created_at
----+-------------------------------
  1 | 2025-09-01 10:15:42.318524+04
(1 row)

INSERT 0 1
PostgreSQL (psql), çalıştırılamaz. RETURNING yeni satırın değerlerini ek bir sorgu olmadan döndürür; +04, Bakü saatidir.
SQL
-- MySQL 8
CREATE TABLE products (
  id         BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name       VARCHAR(100)   NOT NULL,
  category   VARCHAR(50),
  price      DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
  stock      INT            NOT NULL DEFAULT 0,
  attributes JSON,
  created_at TIMESTAMP      NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
Beklenen çıktı
Query OK, 0 rows affected (0.03 sec)
MySQL'de utf8, eski 3 baytlık kodlamadır (utf8mb3) ve emojiler gibi bazı karakterleri saklayamaz; her zaman utf8mb4 yaz (MySQL 8.0'da varsayılan budur).
SQL
SELECT 0.1 + 0.2   AS a,
       1450 * 0.07 AS b,
       899.99 * 3  AS c;
▸ Beklenen çıktı
a | b | c
0.30000000000000004 | 101.50000000000001 | 2699.9700000000003
Örnek 1: bir tabloyu SQLite'tan PostgreSQL'e taşımak

Alıştırma veritabanındaki orders(id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, quantity INTEGER, order_date TEXT) tablosunu PostgreSQL için yeniden yaz. Hangi tipleri ve kısıtlamaları seçerdin?

Çözümü göster
1) id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY — milyarlarca sipariş için yer, standart yazım.
2) customer_id BIGINT NOT NULL REFERENCES customers(id) ve product_id BIGINT NOT NULL REFERENCES products(id) — PostgreSQL yabancı anahtarları her zaman denetler; SQLite'taki gibi PRAGMA gerekmez.
3) quantity INTEGER NOT NULL CHECK (quantity > 0) — eksi ya da sıfır miktar iş kuralına aykırıdır.
4) order_date DATE NOT NULL DEFAULT CURRENT_DATE — metin değil, gerçek bir tarih: geçersiz bir tarih ('2025-02-30') girilemez ve tarih fonksiyonları doğrudan çalışır.
5) Yabancı anahtarlara indeks: CREATE INDEX ON orders (customer_id) — PostgreSQL bunları otomatik oluşturmaz.

JSON sütunları

Ürün özellikleri kategoriden kategoriye değişir: dizüstü bilgisayarın belleği, monitörün ekran boyutu vardır. Bu tür değişken nitelikleri bir JSON sütununda saklamak pratiktir. PostgreSQL'de JSONB ikili biçimde saklanır; anahtarlara -> (JSON döndürür) ve ->> (metin döndürür) ile erişilir, @> ise bir GIN indeksiyle hızlanan “içerir” denetimidir. MySQL'de JSON tipi ve ->>'$.anahtar' yazımı vardır. SQLite'ta da JSON fonksiyonları bulunur; aşağıdaki örneği çalıştır.

SQL
-- PostgreSQL
CREATE INDEX idx_products_attr ON products USING GIN (attributes);

SELECT name,
       attributes ->> 'color'          AS color,
       (attributes ->> 'ram_gb')::int AS ram
FROM products
WHERE attributes @> '{"color": "silver"}';
Beklenen çıktı
  name  | color  | ram
--------+--------+-----
 Laptop | silver |  16
(1 row)
PostgreSQL, çalıştırılamaz. ::int, PostgreSQL'in tip dönüştürme yazımıdır (CAST(... AS INT) ile aynı).
SQL
-- SQLite (3.38+): JSON stored as text
CREATE TABLE gadgets (id INTEGER PRIMARY KEY, name TEXT, attributes TEXT);
INSERT INTO gadgets (name, attributes) VALUES
  ('Laptop',     '{"ram_gb": 16, "color": "silver"}'),
  ('Smartphone', '{"ram_gb": 8, "color": "black"}'),
  ('Tablet',     '{"ram_gb": 8, "color": "silver"}'),
  ('Monitor',    '{"size_in": 27}');

SELECT name,
       attributes ->> '$.color'  AS color,
       attributes ->> '$.ram_gb' AS ram_gb
FROM gadgets
ORDER BY id;
▸ Beklenen çıktı
name | color | ram_gb
Laptop | silver | 16
Smartphone | black | 8
Tablet | silver | 8
Monitor | NULL | NULL

Kullanıcılar, roller ve yetkiler

Bir sunucu VTYS'si her bağlantının kime ait olduğunu bilir ve her işlemi izinlerle denetler. Temel kural en az yetki ilkesidir: her kullanıcı yalnızca işi için gereken hakları alır. Bir uygulama asla süper kullanıcı (postgres, root) olarak bağlanmamalıdır: SQL enjeksiyonu gibi bir zayıflık varsa saldırgan tüm veritabanını silebilir.

Örnek 2: bir okul uygulaması için roller

school veritabanı için iki rol gerekiyor: web uygulaması verileri okuyup değiştirebilmeli ama tablo oluşturup silememeli; analist ise yalnızca okuyabilmeli. Bu, PostgreSQL'de nasıl kurulur?

Çözümü göster
1) Her rol için: CREATE ROLE ... LOGIN ve GRANT CONNECT ON DATABASE school.
2) Şemayı kullanma hakkı: GRANT USAGE ON SCHEMA public; CREATE hakkı verilmez, bu yüzden tablo oluşturulamaz.
3) Uygulamaya: GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public; analiste yalnızca GRANT SELECT.
4) ALL TABLES yalnızca var olan tabloları kapsar; gelecekteki tablolar için ALTER DEFAULT PRIVILEGES yazılır.
5) Tabloların sahibi ayrı bir “geçiş” (migration) rolüdür ve yapı değişiklikleri yalnızca onunla yapılır.
SQL
-- PostgreSQL
CREATE ROLE school_app LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE school TO school_app;
GRANT USAGE ON SCHEMA public TO school_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO school_app;

CREATE ROLE analyst LOGIN PASSWORD 'change-me-too';
GRANT CONNECT ON DATABASE school TO analyst;
GRANT USAGE ON SCHEMA public TO analyst;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst;

-- MySQL: CREATE USER 'school_app'@'10.0.0.%' IDENTIFIED BY '...';
--        GRANT SELECT, INSERT, UPDATE, DELETE ON school.* TO 'school_app'@'10.0.0.%';
Beklenen çıktı
CREATE ROLE
GRANT
GRANT
GRANT
CREATE ROLE
GRANT
GRANT
GRANT
Parolaları asla kodda tutma; ortam değişkenlerinde ya da bir sır deposunda sakla. MySQL'de bir kullanıcı 'ad'@'host' çiftiyle tanımlanır.

Yedekler ve geri yükleme

Mantıksal bir yedek, veritabanını SQL komutlarına ya da özel bir arşive dönüştürür: PostgreSQL'de pg_dump, MySQL'de mysqldump. Sürümler arasında taşıma için kullanışlıdır, ama yalnızca alındığı anı saklar. Fiziksel bir yedek (pg_basebackup) ile günlük dosyalarının arşivlenmesi (WAL archiving) ise veritabanını herhangi bir ana geri döndürmeyi (point-in-time recovery) sağlar. SQLite'ta bunun için VACUUM INTO 'dosya.db' ya da kabuktaki .backup komutu vardır.

Terminal
# PostgreSQL: custom-format dump and a test restore into another database
pg_dump -U postgres -d school -F c -f school_2025-09-01.dump
createdb -U postgres school_test
pg_restore -U postgres -d school_test school_2025-09-01.dump

# MySQL: a consistent InnoDB dump without locking tables
mysqldump -u root -p --single-transaction school > school_2025-09-01.sql

ls -lh school_2025-09-01.*
Beklenen çıktı
-rw-r--r-- 1 admin admin 2.4M Sep  1 03:00 school_2025-09-01.dump
-rw-r--r-- 1 admin admin 7.9M Sep  1 03:02 school_2025-09-01.sql
Başarılı pg_dump ve pg_restore hiçbir şey yazdırmaz; dosya boyutları örnektir.
Alıştırma

Başlangıç kodundaki gadgets tablosundan rengi silver olan cihazların adını ve belleğini (ram_gb) JSON işleci ->> ile seç. Sonucu ada göre sırala.

Alıştırma · SQL
CREATE TABLE gadgets (id INTEGER PRIMARY KEY, name TEXT, attributes TEXT);
INSERT INTO gadgets (name, attributes) VALUES
  ('Laptop',     '{"ram_gb": 16, "color": "silver"}'),
  ('Smartphone', '{"ram_gb": 8, "color": "black"}'),
  ('Tablet',     '{"ram_gb": 8, "color": "silver"}'),
  ('Monitor',    '{"size_in": 27}');

-- your SELECT here
▸ Beklenen çıktı
name | ram_gb
Laptop | 16
Tablet | 8
Alıştırma

Depodaki malların toplam değerini kayan nokta hatası olmadan hesapla: her fiyatı manatın yüzde birine çevir (CAST(ROUND(price * 100) AS INTEGER)), stokla çarp ve topla (total_qapik), sonra manat olarak göster (total_manat).

Alıştırma · SQL
SELECT SUM(price * stock) AS float_total   -- replace with exact integer math
FROM products;
▸ Beklenen çıktı
total_qapik | total_manat
4026060 | 40260.6

Önemli noktalar

  • SQLite tek bir dosyada yaşayan bir kütüphanedir; PostgreSQL ve MySQL ise katı tipli, çok sayıda yazıcıyı destekleyen istemci–sunucu sistemleridir.
  • Para için NUMERIC/DECIMAL, otomatik anahtar için GENERATED ... AS IDENTITY (PostgreSQL) ve AUTO_INCREMENT (MySQL).
  • JSONB ve JSON değişken nitelikler içindir; aranan ya da kısıtlama gerektiren alanlar sıradan sütun olmalıdır.
  • En az yetki: uygulama, analistler ve geçişler için ayrı roller; uygulama asla süper kullanıcı olarak bağlanmaz.
  • Mantıksal (pg_dump, mysqldump) ve fiziksel yedekler ile WAL arşivi; 3-2-1 kuralı ve düzenli geri yükleme testi.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
PostgreSQL'de bir ürün fiyatı için en doğru tip hangisidir?