Skip to content
Bloga Dönün
Eğitimler

PostgreSQL timestamp Sütununda Tam Olarak Ne Saklanır?

PostgreSQL'in timestamp ve timestamptz'i nasıl sakladığına, saat dilimlerinin neden başınızı ağrıttığına ve kullanım durumunuz için doğru türü nasıl seçeceğinize dair sade bir rehber.

6 dakika okuma

PostgreSQL timestamp ve timestamptz: Kaputun Altında Aslında Ne Saklanır?

PostgreSQL hem timestamp’i hem de timestamptz’i tek bir 64 bitlik tam sayı olarak tutar: 1970-01-01 00:00:00 UTC’den bu yana geçen mikrosaniyelerin sayısı. Ayrım yalnızca veriyi insanların okuyabileceği biçime dönüştürürken ortaya çıkar.

Bu İnsanların Aklını Neden Karıştırır?

  • İki sütun, tek bir tarih… iki farklı sorgu sonucu
  • Uygulamanız 2025-07-29 10:00 ekler, ama başka bir ekip 02:00 görür
  • Ön uç, arka uç logundakiyle eşleşmeyen bir ISO string’i oluşturur

İki Şeftali Konservesi: Biri Sade, Biri Etiketli

Veri TürüResmi AdıSaklanan DeğerSELECT Üzerinde Ne Olur
timestamptimestamp without time zoneham mikrosaniye sayımıDeğiştirilmeden geri gönderilir — Postgres asla bir saat dilimi tahmin etmez
timestamptztimestamp with time zoneaynı mikrosaniye sayımıPostgres, metni göndermeden hemen önce oturumun TimeZone ayarını uygular

Benzetme

  • timestamp = köken etiketi olmayan bir şeftali kavanozu. Meyve olduğunu bilirsiniz, ama nerede konservelendiğini değil.
  • timestamptz = “UTC+8’de Üretildi” yazan, gururla damgalanmış bir kavanoz. Açan herkes, besin değeri panelini dönüştürüp dönüştürmemeye karar verebilir.

Kaputun Altında: Sadece Devasa Bir Sayı

2000-01-01 00:00:00 UTC  → 0
2000-01-01 00:00:01 UTC  → 1 000 000
  • Birim: mikrosaniye (saniyenin milyonda biri)
  • Aralık: MÖ 4713 – MS 294276 — Indiana Jones onaylı
  • timestamp ve timestamptz için depolama birebir aynıdır; yorumlama farklıdır

15 Saniyelik Bir Demo

-- İstemci Şangay saatinde düşünüyor
SET TimeZone = 'Asia/Shanghai';

CREATE TABLE demo (
  created_ts timestamp,
  created_tz timestamptz
);

INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
SorguSonuçNeden
SELECT created_ts FROM demo;2025-07-29 10:00:00Ham değer, TZ aritmetiği yok
SELECT created_tz FROM demo;2025-07-29 10:00:00+08Etiket, çıktıda uygulanır
SET TimeZone = 'UTC'; ardından select2025-07-29 02:00:00+00Aynı an, yeni mercek

Zaman Damgası Aritmetiği ve Aralıklar

PostgreSQL zaman damgalarının en pratik yönlerinden biri, aralık (interval) aritmetiğidir. Her iki tür de mikrosaniye sayımları sakladığı için aralıkları doğrudan toplayıp çıkarabilirsiniz:

-- 3 saat 30 dakika ekle
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08

-- İki zaman damgası arasındaki farkı bul
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00  (bir aralık)

-- Belirli alanları çıkar
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800  (saniye cinsinden Unix zaman damgası)

-- Gün sınırına yuvarla (günlük toplamalar için yararlı)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08

EXTRACT(EPOCH FROM ...) fonksiyonu, zaman damgalarını Unix epoch saniyesi bekleyen dış sistemlere geçirmeniz gerektiğinde özellikle yararlıdır. Tersine, bir epoch değerini tekrar zaman damgasına dönüştürebilirsiniz:

SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08  (Asia/Shanghai oturumunda)

İnce ama önemli bir nokta: timestamp’in (saat dilimsiz) aralık aritmetiği DST geçişlerini tamamen yok sayarken, timestamptz bunları gözetir. Bu, bir DST sınırını aşan bir timestamptz değerine INTERVAL '1 day' eklemenin, tam olarak 24 saat sonrasını değil — aynı duvar saati saatini doğru biçimde döndüreceği anlamına gelir.

Index’leme ve Performans Hususları

Hem timestamp hem de timestamptz, 8 baytlık tam sayılar olarak saklanır; bu nedenle depolama veya index’leme açısından aralarında performans farkı yoktur. B-tree index’leri her iki tür üzerinde aynı şekilde çalışır, çünkü temel karşılaştırma yalnızca tam sayı karşılaştırmasıdır.

Ne var ki, birkaç pratik husus vardır:

  • Aralık sorguları: WHERE created_at > '2025-07-01' her iki tür üzerindeki bir index’le verimli biçimde çalışır. timestamptz ile PostgreSQL, karşılaştırmadan önce sabit değeri UTC’ye dönüştürür, böylece index yine kullanılır.
  • Bölümleme anahtarları: Zaman damgası sütunlarında aralık bölümlemesi (range partitioning) kullanırken timestamptz genellikle daha güvenlidir, çünkü bölüm sınırları belirsizlik içermez (her zaman UTC). timestamp ile '2025-07-01 00:00' gibi bir sınır, farklı oturumlar için farklı şeyler ifade edebilir.
  • Fonksiyonel index’ler: Sıklıkla yalnızca tarihe göre (saati yok sayarak) sorgu yapıyorsanız, günlük toplama sorgularını hızlandırmak için date_trunc('day', created_at) üzerinde bir index düşünün.

Yaygın Tuzaklar ve Hızlı Çözümler

1. Farklı kullanıcılar, farklı saatler

  • Sebep: istemciler timestamptz ile farklı TimeZone ayarları kullanır
  • Çözüm: ya her şeyi timestamp tutup tek bir saat dilimi üzerinde anlaşın, ya da bağlantı başlatma sırasında SET TimeZone = 'UTC' zorlayın

Uygulama kodunda yaygın bir desen, saat dilimini bağlantı havuzu başlatma sırasında bir kez ayarlamaktır:

-- Bağlantı kurulumunda (ör. pg pool yapılandırması)
SET timezone = 'UTC';

Bu, tüm oturumların aynı UTC temsilini görmesini sağlar; uygulama katmanınız ise gösterim için yerel saate dönüşümü ele alır.

2. “Duvar saati” saklamak ama yanlış türü seçmek

  • İş takvimleri (mağaza saatleri, son ödeme tarihleri) timestamp kullanmalıdır
  • Sınır ötesi iş akışları (siparişler, loglar) timestamptz içinde UTC saklamalıdır

Test basittir: soru “bu, zamanın hangi anında oldu?” ise timestamptz kullanın. Soru “duvardaki saat ne diyor?” ise timestamp kullanın.

3. Sürüklenen API’ler

  • timestamptz’i her zaman offset (Z veya +08:00) içeren ISO 8601 string’leri olarak gönderin
  • Biçimlendirmeyi yerel olarak UI’a bırakın

4. Türler arası zaman damgası karşılaştırması

Karşılaştırmalarda veya birleştirmelerde (join) timestamp ile timestamptz’i karıştırmak, ince hataların yaygın bir kaynağıdır:

-- Tehlikeli: örtük dönüşüm oturum saat dilimini uygular
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL, oturum saat dilimini kullanarak s.start_ts'i timestamptz'e dönüştürür
-- Farklı oturumlar farklı join sonuçları alabilir!

Çözüm: türler arası karşılaştırma yaparken her zaman açıkça cast yapın veya etki alanı başına tek bir tür üzerinde standartlaşın.

5. ORM varsayılan tuzakları

Birçok ORM (Django, SQLAlchemy, ActiveRecord) varsayılan olarak saat dilimsiz timestamp kullanır. Şema taşıması (migration) dosyalarınızı denetleyin — uygulamanız saat dilimleri arasındaki kullanıcılara hizmet veriyorsa varsayılanı timestamptz’e geçirin. Django’da ayarlarda USE_TZ = True belirleyin. SQLAlchemy’de DateTime(timezone=True) kullanın.

Hile Sayfası: Hangisini Kullanmalıyım?

Yalnızca yerel takvim → timestamp
Küresel olan her şey  → timestamptz (UTC saklayın)
  • Finansal raporlar, ders programları → timestamp
  • Denetim logları, e-ticaret siparişleri → timestamptz

Go Tools ile Saniyeler İçinde Doğrulayın

İhtiyaçAraçNasıl
SQL’den epoch değerini incelemekEpoch Dönüştürücü1690622400 yapıştırın, Dönüştür’e basın
Zaman alanları içeren toplu JSON’u düzenlemekJSON BiçimlendiriciYükü bırakın, biçimlendirip tarayın

Tüm yardımcı araçlar tamamen tarayıcınızda çalışır — hiçbir veri makinenizden ayrılmaz.

Sıkça Sorulan Sorular

PostgreSQL’de timestamp ile timestamptz arasındaki fark nedir?

timestamp (saat dilimsiz), bir tarih-saat değerini olduğu gibi, hiçbir saat dilimi bağlamı olmadan saklar. timestamptz (saat dilimli), girdiyi depolama için UTC’ye dönüştürür ve veriyi alırken oturumun saat dilimine geri dönüştürür. Neredeyse her durum için timestamptz kullanın — dağıtık sistemler genelinde saat dilimi kaynaklı hataları önler.

PostgreSQL gerçekten saat dilimini timestamptz içinde saklar mı?

Hayır — adına rağmen, PostgreSQL saat diliminin kendisini saklamaz. Girdiyi UTC’ye dönüştürür ve yalnızca UTC değerini saklar (2000-01-01’den itibaren bir mikrosaniye sayımı). Veri alımı sırasında, UTC’den oturumunuzun timezone ayarının belirttiği saat dilimine dönüştürür. Orijinal saat dilimi bilgisi atılır.

PostgreSQL oturumunun saat dilimini nasıl değiştiririm?

Oturum saat dilimini değiştirmek için SET timezone = 'America/New_York'; çalıştırın. Bu, timestamptz değerlerinin nasıl gösterileceğini ve yorumlanacağını etkiler. Sunucu genelindeki varsayılanlar için postgresql.conf içinde timezone ayarlayın. Belirsizlikten kaçınmak için kısaltmalar (CST gibi) yerine her zaman IANA saat dilimi adlarını (Asia/Shanghai gibi) kullanın.

Olay zamanlarını saklamak için timestamp mi yoksa timestamptz mi kullanmalıyım?

Neredeyse her şey için timestamptz kullanın — kullanıcı eylemleri, API çağrıları, denetim logları ve zamanlanmış olaylar. timestamp’i (saat dilimsiz) yalnızca belirli bir ana bağlı olmayan soyut zamanlar için kullanın; örneğin “mağaza 09:00’da açılır” — burada kastedilen, belirli bir UTC anı değil, hangi yerel saat dilimiyse oradaki sabah 9’dur.

PostgreSQL, timestamptz ile yaz saati uygulamasını nasıl ele alır?

PostgreSQL, timestamptz kullanırken DST’yi doğru biçimde ele alır, çünkü her şeyi dahili olarak UTC içinde saklar. Bir değer aldığınızda, PostgreSQL UTC’den oturum saat diliminizin geçerli DST kurallarını kullanarak dönüştürür. Bu, aynı saklanan UTC anının, bir DST geçişinden önce ve sonra farklı yerel saatleri doğru biçimde göstereceği anlamına gelir.

Unix zaman damgalarına ilişkin kapsamlı bir rehber için — hassasiyet yönetimi, saat dilimi iyi pratikleri ve JavaScript, Python ve Go’daki kod örnekleri dahil — Unix Zaman Damgası Rehberi’mize bakın.

Toparlayalım

  • Her iki Postgres zaman türü de mikrosaniye sayaçlarıdır; bütün fark etikettedir
  • Yanlış olanı seçmek, kafa karıştırıcı zaman damgaları ve bozuk aritmetik anlamına gelir
  • Saatlerce hata ayıklamadan kurtulmak için doğru araçlarla test edin, dönüştürün ve sağduyu denetimi yapın