MariaDB ve MySQL’de NOW() ve CURDATE() ile Tarih Fonksiyonları
Veritabanı sorgularında zaman damgası almak, kayıtları tarihe göre filtrelemek ya da iki tarih arasındaki farkı hesaplamak günlük sysadmin hayatının vazgeçilmez parçalarından biri. MariaDB ve MySQL’de bu işlerin büyük çoğunluğu NOW() ve CURDATE() fonksiyonlarının etrafında döner. Bu iki fonksiyon ilk bakışta basit görünse de doğru kullanıldıklarında çok güçlü sorgular yazmanı sağlar. Bu yazıda hem bu fonksiyonları derinlemesine ele alacağız hem de gerçek dünya senaryolarıyla birlikte tarih işlemlerinde sık kullanılan diğer yardımcı fonksiyonlara da değineceğiz.
NOW() ve CURDATE() Arasındaki Fark
Her şeyden önce şunu netleştirelim: NOW() ve CURDATE() birbiriyle karıştırılan iki fonksiyon. İkisi de “şu anki zamanı verir” gibi algılanıyor ama aralarında kritik bir fark var.
NOW(): Hem tarihi hem de saati döndürür. Format olarak YYYY-MM-DD HH:MM:SS şeklindedir.
CURDATE(): Sadece tarihi döndürür. Format olarak YYYY-MM-DD şeklindedir.
mysql -u root -p testdb -e "SELECT NOW(), CURDATE(), CURTIME();"
Bunu çalıştırdığında şöyle bir çıktı alırsın:
# Örnek çıktı:
# +---------------------+------------+-----------+
# | NOW() | CURDATE() | CURTIME() |
# +---------------------+------------+-----------+
# | 2024-03-15 14:32:10 | 2024-03-15 | 14:32:10 |
# +---------------------+------------+-----------+
CURTIME() da var gördüğün gibi, sadece saati döndürür. Bu üçlüyü aklında tutarsan tarih/saat işlemlerinin büyük kısmını halledersin.
Bir de SYSDATE() var. NOW() ile kardeş gibi görünür ama farkları önemli: NOW() sorgunun başladığı andaki zamanı döndürürken SYSDATE() her çağrıldığı andaki zamanı döndürür. Uzun çalışan sorgularda bu fark ortaya çıkar.
Temel Kullanım Senaryoları
Aktif Oturumları Sorgulamak
Bir web uygulaması yönetiyorsun ve kullanıcı oturumlarını takip eden bir tablun var. Son 30 dakika içinde aktif olan kullanıcıları bulmak istiyorsun:
mysql -u root -p uygulamam -e "
SELECT kullanici_id, kullanici_adi, son_aktif
FROM oturumlar
WHERE son_aktif >= NOW() - INTERVAL 30 MINUTE
ORDER BY son_aktif DESC;
"
Bu sorgu sysadmin açısından altın değerinde. Bir performans sorunu yaşandığında ya da güvenlik denetimi yapacağında anlık aktif oturumları görmek isteyeceksin.
Bugün Oluşturulan Kayıtları Çekmek
Log tablosu olan bir sistem düşün. Sadece bugünkü hataları görmek istiyorsun:
mysql -u root -p logdb -e "
SELECT hata_kodu, mesaj, olusturulma_zamani
FROM sistem_loglari
WHERE DATE(olusturulma_zamani) = CURDATE()
AND seviye = 'ERROR'
ORDER BY olusturulma_zamani DESC;
"
Burada DATE() fonksiyonunu kullandığımıza dikkat et. olusturulma_zamani alanı DATETIME tipindeyse, CURDATE() ile direkt karşılaştıramazsın çünkü CURDATE() saat kısmı olmadan döner. DATE() fonksiyonu ile DATETIME alanının sadece tarih kısmını alıp karşılaştırıyoruz.
INTERVAL ile Tarih Aritmetiği
Tarih işlemlerinin kalbi INTERVAL kullanımında atıyor. Hem geçmişe hem de geleceğe doğru hesaplama yapabilirsin.
mysql -u root -p testdb -e "
SELECT
NOW() AS simdi,
NOW() + INTERVAL 1 DAY AS yarin,
NOW() - INTERVAL 7 DAY AS bir_hafta_once,
NOW() + INTERVAL 1 MONTH AS bir_ay_sonra,
NOW() - INTERVAL 1 YEAR AS bir_yil_once,
NOW() + INTERVAL 2 HOUR AS iki_saat_sonra,
NOW() - INTERVAL 30 MINUTE AS otuz_dakika_once;
"
INTERVAL ile kullanabileceğin birimler:
- MICROSECOND: Mikrosaniye
- SECOND: Saniye
- MINUTE: Dakika
- HOUR: Saat
- DAY: Gün
- WEEK: Hafta
- MONTH: Ay
- QUARTER: Çeyrek yıl
- YEAR: Yıl
- MINUTE_SECOND: Dakika ve saniye birlikte (format: ‘dakika:saniye’)
- HOUR_MINUTE: Saat ve dakika birlikte
- DAY_HOUR: Gün ve saat birlikte
- YEAR_MONTH: Yıl ve ay birlikte
Gerçek Dünya Senaryosu: Yedekleme Takip Sistemi
Birden fazla sunucunun yedeklerini takip eden bir sistem yönetiyorsun diyelim. Yedekleme durumlarını tutan bir tablun var:
mysql -u root -p yedekdb -e "
CREATE TABLE IF NOT EXISTS yedekleme_durumu (
id INT AUTO_INCREMENT PRIMARY KEY,
sunucu_adi VARCHAR(100),
yedek_turu VARCHAR(50),
tamamlanma_zamani DATETIME,
durum VARCHAR(20),
boyut_mb INT
);
"
Bu tabloya veri eklerken NOW() kullanımı:
mysql -u root -p yedekdb -e "
INSERT INTO yedekleme_durumu (sunucu_adi, yedek_turu, tamamlanma_zamani, durum, boyut_mb)
VALUES
('web01', 'tam_yedek', NOW(), 'basarili', 4500),
('db01', 'artimli_yedek', NOW(), 'basarili', 890),
('mail01', 'tam_yedek', NOW() - INTERVAL 2 DAY, 'basarili', 2100);
"
Şimdi son 24 saat içinde yedeklenmemiş sunucuları bulmak için:
mysql -u root -p yedekdb -e "
SELECT sunucu_adi,
MAX(tamamlanma_zamani) AS son_yedek,
TIMESTAMPDIFF(HOUR, MAX(tamamlanma_zamani), NOW()) AS saat_fark
FROM yedekleme_durumu
WHERE durum = 'basarili'
GROUP BY sunucu_adi
HAVING MAX(tamamlanma_zamani) < NOW() - INTERVAL 24 HOUR
ORDER BY saat_fark DESC;
"
Bu sorgu sana kaç saattir yedeklenmeyen sunucuların listesini verir. Bunu bir cron job’a koyup e-posta bildirimi gönderecek şekilde ayarlayabilirsin.
TIMESTAMPDIFF ve DATEDIFF Fonksiyonları
İki tarih arasındaki farkı hesaplamak için bu iki fonksiyon çok işe yarar.
DATEDIFF: Sadece gün farkını hesaplar.
TIMESTAMPDIFF: Belirttiğin birimde fark hesaplar.
mysql -u root -p testdb -e "
SELECT
DATEDIFF(NOW(), '2024-01-01') AS yilin_kacinci_gunu,
TIMESTAMPDIFF(HOUR, '2024-03-01 08:00:00', NOW()) AS saat_gecti,
TIMESTAMPDIFF(DAY, '2024-01-01', CURDATE()) AS gun_fark,
TIMESTAMPDIFF(MONTH, '2023-01-01', CURDATE()) AS ay_fark,
TIMESTAMPDIFF(YEAR, '1990-05-15', CURDATE()) AS yas_hesabi;
"
Özellikle TIMESTAMPDIFF ile yaş hesabı yapmak çok pratik. Kullanıcı kayıt sistemlerinde sıkça lazım olur.
Sertifika ve Lisans Takip Sistemi
SSL sertifikalarının veya yazılım lisanslarının bitiş tarihlerini takip eden bir tablo düşün:
mysql -u root -p altyapi_db -e "
SELECT
sertifika_adi,
alan_adi,
bitis_tarihi,
DATEDIFF(bitis_tarihi, CURDATE()) AS kalan_gun,
CASE
WHEN DATEDIFF(bitis_tarihi, CURDATE()) < 0 THEN 'SURESI_DOLMUS'
WHEN DATEDIFF(bitis_tarihi, CURDATE()) <= 7 THEN 'KRITIK'
WHEN DATEDIFF(bitis_tarihi, CURDATE()) <= 30 THEN 'UYARI'
ELSE 'NORMAL'
END AS durum
FROM ssl_sertifikalari
WHERE bitis_tarihi <= CURDATE() + INTERVAL 60 DAY
ORDER BY bitis_tarihi ASC;
"
Bu sorgu önümüzdeki 60 gün içinde süresi dolacak sertifikaları ve her birinin durumunu gösterir. Bunu bir monitoring script’ine entegre etmek oldukça basit.
DATE_FORMAT ile Tarih Formatlama
Sorgu sonuçlarını raporlamak için ya da farklı formatlarda çıktı almak için DATE_FORMAT fonksiyonu kullanılır:
mysql -u root -p rapordb -e "
SELECT
DATE_FORMAT(NOW(), '%d.%m.%Y') AS turkce_format,
DATE_FORMAT(NOW(), '%Y-%m-%d') AS iso_format,
DATE_FORMAT(NOW(), '%d/%m/%Y %H:%i:%s') AS detayli_format,
DATE_FORMAT(NOW(), '%W, %d %M %Y') AS uzun_format,
DATE_FORMAT(CURDATE(), '%Y%m%d') AS dosya_adi_icin;
"
DATE_FORMAT için sık kullanılan format belirteçleri:
- %Y: 4 haneli yıl (2024)
- %y: 2 haneli yıl (24)
- %m: 2 haneli ay (01-12)
- %M: Ay adı (January, February…)
- %d: 2 haneli gün (01-31)
- %H: 24 saat formatında saat (00-23)
- %h: 12 saat formatında saat (01-12)
- %i: Dakika (00-59)
- %s: Saniye (00-59)
- %W: Gün adı (Monday, Tuesday…)
- %p: AM veya PM
Dosya adı oluştururken %Y%m%d formatı çok işe yarar. Yedekleme script’lerinde tarihi dosya adına eklemek için klasik bir yöntem.
Haftalık ve Aylık Raporlar için Tarih Sınırları
Sysadmin olarak periyodik raporlar hazırlamak zorunda kalırsın. Bu tür raporlar için haftanın başını, ayın başını bulmak gerekir:
mysql -u root -p rapordb -e "
SELECT
CURDATE() AS bugun,
DATE(NOW() - INTERVAL WEEKDAY(NOW()) DAY) AS bu_haftanin_pazartesi,
DATE_FORMAT(CURDATE(), '%Y-%m-01') AS bu_ayin_ilk_gunu,
LAST_DAY(CURDATE()) AS bu_ayin_son_gunu,
DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m-01') AS gecen_ayin_ilk_gunu,
LAST_DAY(CURDATE() - INTERVAL 1 MONTH) AS gecen_ayin_son_gunu;
"
Bu sorguyu bir stored procedure içine alıp rapor tarih aralıklarını otomatik hesaplatabilirsin. Her ay başında elle tarih yazmak yerine bu hesaplamaları kullanmak hem zaman kazandırır hem de hata riskini ortadan kaldırır.
Performans İpucu: İndeks Kullanımına Dikkat
Tarih sorgularında çok sık yapılan bir hata indeks kullanımını bozmak. Şunu iyi anlamak lazım: Eğer indeksli bir tarih kolonuna fonksiyon uygularsan MySQL/MariaDB bu indeksi kullanamaz.
Yanlış kullanım:
# Bu sorgu full table scan yapar, indeksi kullanamaz
mysql -u root -p logdb -e "
SELECT * FROM sistem_loglari
WHERE DATE(olusturulma_zamani) = CURDATE();
"
Doğru kullanım:
# Bu sorgu indeksi kullanabilir
mysql -u root -p logdb -e "
SELECT * FROM sistem_loglari
WHERE olusturulma_zamani >= CURDATE()
AND olusturulma_zamani < CURDATE() + INTERVAL 1 DAY;
"
İkinci sorgu mantıksal olarak aynı sonucu verir ama olusturulma_zamani kolonu üzerinde bir indeks varsa bunu aktif olarak kullanır. Büyük tablolarda bu fark sorgu süresini saniyelerden milisaniyelere indirebilir.
Benzer şekilde geçen ayın kayıtlarını çekerken:
mysql -u root -p logdb -e "
EXPLAIN SELECT * FROM sistem_loglari
WHERE olusturulma_zamani >= DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m-01')
AND olusturulma_zamani < DATE_FORMAT(CURDATE(), '%Y-%m-01');
"
EXPLAIN ile sorgu planını incelemek her zaman iyi bir alışkanlık.
Zaman Dilimi Yönetimi
Birden fazla ülkede hizmet veren sistemlerde zaman dilimi yönetimi kritik önem taşır. MariaDB/MySQL’de NOW() sunucunun sistem saatini kullanır. Bunu kontrol etmek için:
mysql -u root -p -e "
SELECT @@global.time_zone, @@session.time_zone, NOW(), UTC_TIMESTAMP();
"
Eğer uygulamanın farklı zaman diliminde çalışmasını istiyorsan:
mysql -u root -p -e "
SET time_zone = 'Europe/Istanbul';
SELECT NOW(), UTC_TIMESTAMP(), CONVERT_TZ(NOW(), 'Europe/Istanbul', 'UTC') AS utc_saat;
"
Birden fazla zaman dilimiyle çalışıyorsanız veritabanında her zaman UTC saklamanızı ve gösterim katmanında dönüşüm yapmanızı öneririm. Yaz saati uygulaması gibi değişiklikler çok baş ağrıtır.
Stored Procedure ile Otomatik Tarih Hesaplaması
Tekrar eden tarih hesaplamalarını stored procedure içine almak hem kod tekrarını önler hem de bakımı kolaylaştırır:
mysql -u root -p rapordb -e "
DELIMITER //
CREATE PROCEDURE aylik_rapor_tarihleri(
IN hedef_ay INT,
IN hedef_yil INT,
OUT baslangic_tarihi DATE,
OUT bitis_tarihi DATE
)
BEGIN
SET baslangic_tarihi = DATE(CONCAT(hedef_yil, '-', LPAD(hedef_ay, 2, '0'), '-01'));
SET bitis_tarihi = LAST_DAY(baslangic_tarihi);
END //
DELIMITER ;
"
Bunu çağırmak için:
mysql -u root -p rapordb -e "
CALL aylik_rapor_tarihleri(3, 2024, @bas, @bit);
SELECT @bas AS baslangic, @bit AS bitis;
"
Monitoring Script’i ile Entegrasyon
Bash script içinde MySQL tarih sorgularını kullanmak günlük operasyonların bir parçası haline gelebilir. İşte veritabanındaki eski kayıtları temizleyen bir örnek script:
#!/bin/bash
DB_HOST="localhost"
DB_USER="temizlik_user"
DB_PASS="gizli_sifre"
DB_NAME="uygulama_db"
RETENTION_DAYS=90
echo "$(date '+%Y-%m-%d %H:%M:%S') - Temizlik basliyor..."
SILINEN=$(mysql -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME -se "
DELETE FROM sistem_loglari
WHERE olusturulma_zamani < NOW() - INTERVAL $RETENTION_DAYS DAY
AND seviye NOT IN ('CRITICAL', 'ERROR');
SELECT ROW_COUNT();
")
echo "$(date '+%Y-%m-%d %H:%M:%S') - $SILINEN kayit silindi."
# Yedekleme durumunu kontrol et
EKSIK_YEDEK=$(mysql -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME -se "
SELECT COUNT(*)
FROM yedekleme_durumu
WHERE tamamlanma_zamani < NOW() - INTERVAL 25 HOUR
AND durum = 'bekliyor';
")
if [ "$EKSIK_YEDEK" -gt 0 ]; then
echo "UYARI: $EKSIK_YEDEK adet tamamlanmamis yedek var!"
fi
Bu script’i cron’a ekleyerek her gece çalıştırabilirsin. Hem eski logları temizler hem de yedekleme durumunu kontrol eder.
UNIX_TIMESTAMP ve FROM_UNIXTIME
Bazı eski sistemler veya API’lar timestamp değerlerini Unix epoch olarak saklar. Bu değerleri dönüştürmek için:
mysql -u root -p testdb -e "
SELECT
UNIX_TIMESTAMP() AS unix_simdi,
UNIX_TIMESTAMP(NOW()) AS now_unix,
FROM_UNIXTIME(1710500000) AS epoch_donusumu,
FROM_UNIXTIME(UNIX_TIMESTAMP(), '%d.%m.%Y %H:%i') AS formatli_donusum;
"
Özellikle Elasticsearch, Redis veya farklı sistemlerden gelen verileri MySQL’e import ederken FROM_UNIXTIME çok işe yarar.
WEEK, MONTH, YEAR Fonksiyonları
Gruplama sorgularında zaman periyoduna göre gruplama yapmak çok yaygın:
mysql -u root -p istatistik_db -e "
SELECT
YEAR(kayit_tarihi) AS yil,
MONTH(kayit_tarihi) AS ay,
WEEK(kayit_tarihi) AS hafta,
DAYOFWEEK(kayit_tarihi) AS haftanin_gunu,
COUNT(*) AS kayit_sayisi
FROM kullanici_kayitlari
WHERE kayit_tarihi >= CURDATE() - INTERVAL 6 MONTH
GROUP BY YEAR(kayit_tarihi), MONTH(kayit_tarihi)
ORDER BY yil, ay;
"
Bu tür sorgular aylık trend analizleri için mükemmel. Kapasiteyi planlarken, yük tahminleri yaparken ya da yöneticiye rapor hazırlarken doğrudan kullanabilirsin.
Sonuç
NOW() ve CURDATE() ilk bakışta basit iki fonksiyon gibi görünse de etraflarındaki ekosistemle birlikte veritabanı operasyonlarının çok güçlü araçlarına dönüşüyor. INTERVAL ile esnek tarih aritmetiği yapabilir, TIMESTAMPDIFF ile kesin zaman farkları hesaplayabilir, DATE_FORMAT ile istediğin formatta çıktı alabilirsin.
Sysadmin günlük işlerinde en çok işine yarayacak kısımlar şunlar olacak: yedekleme takibi için TIMESTAMPDIFF, eski kayıt temizliği için NOW() - INTERVAL, sertifika süresi için DATEDIFF ve monitoring script’lerini beslemek için bash içinden yapılan tarih sorguları. İndeks performansına dikkat etmek ise uzun vadede seni çok büyük sorunlardan kurtarır, DATE() fonksiyonunu bir kolona uygulamak yerine aralık sorgusu kullanma alışkanlığı edinmek bu işin temel kurallarından biri.
Bu fonksiyonları ezberlemek yerine mantığını kavramak önemli. Temel birkaç sorguyu anlayıp EXPLAIN ile planlarını incelemeye başladığında geri kalanı kendiliğinden gelecek.
