PHP ve MySQL ile Sipariş Oluştururken Stok Nasıl Korunur?
Aynı ürüne eş zamanlı gelen siparişlerde stok eksiye düşmeden nasıl işlem yapılır? PHP, PDO ve MySQL ile transaction temelli bir örnek üzerinden adım adım inceleyelim.
Bir ürünün stokta yalnızca bir adet kaldığını düşünelim. Aynı anda iki müşteri satın alma butonuna bastığında iki istek de stok miktarını bir olarak görebilir. Uygulama önce stoğu okuyup sonra siparişi oluşturuyorsa, iki istek de başarılı olabilir. Sonuç: elde tek ürün varken iki sipariş.
Bu yazıda PHP ve MySQL kullanan bir uygulamada bu durumu daha güvenli yönetmenin temel bir yolunu göstereceğim. Örnekte amaç; stoktan düşme ve sipariş kaydını aynı işlem içinde tutmak. Ödeme altyapısının veya eksiksiz bir e-ticaret sisteminin tamamını yazmıyoruz.
Sorun nerede başlıyor?
İlk bakışta şu akış yeterli görünebilir:
- Ürünün stok miktarını oku.
- İstenen miktar kadar stok varsa siparişi oluştur.
- Stok miktarını güncelle.
Ancak okuma ve güncelleme arasında geçen sürede başka bir istek aynı ürünü okuyabilir. Özellikle indirim, kampanya veya sınırlı stok gibi durumlarda bu yarış koşulu daha görünür hâle gelir.
Transaction kullanmak, ilgili veritabanı değişikliklerinin birlikte tamamlanmasını sağlar. Fakat tek başına beginTransaction() yazmak eş zamanlılık sorununu otomatik çözmez. Stok kontrolünün ve stok güncellemesinin de doğru yapılması gerekir.
Örnek tablolar
Basit bir deneme için MySQL üzerinde iki tablo oluşturalım. InnoDB, transaction desteği ve satır kilitleme davranışı nedeniyle bu örnek için önemlidir:
CREATE TABLE products (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
stock INT UNSIGNED NOT NULL DEFAULT 0
) ENGINE=InnoDB;
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT UNSIGNED NOT NULL,
quantity INT UNSIGNED NOT NULL,
status VARCHAR(30) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT orders_product_fk FOREIGN KEY (product_id)
REFERENCES products(id)
) ENGINE=InnoDB;
INSERT INTO products (name, stock) VALUES ('Örnek Ürün', 1);
Bunlar konuyu anlaşılır kılmak için hazırlanmış örnek tablolardır. Gerçek bir mağazada kullanıcı, fiyat, para birimi, sipariş kalemleri ve ödeme gibi alanlar ayrıca tasarlanmalıdır.
Stoğu tek sorguda kontrol edip azaltmak
Aşağıdaki fonksiyonda stok miktarını önce okuyup sonra azaltmak yerine, iki işi tek bir UPDATE sorgusunda yapıyoruz:
<?php
function createOrder(PDO $pdo, int $productId, int $quantity): int
{
if ($productId < 1 || $quantity < 1) {
throw new InvalidArgumentException('Geçersiz ürün veya miktar.');
}
$pdo->beginTransaction();
try {
$update = $pdo->prepare(
'UPDATE products
SET stock = stock - :amount
WHERE id = :product_id AND stock >= :required'
);
$update->execute([
'amount' => $quantity,
'product_id' => $productId,
'required' => $quantity,
]);
if ($update->rowCount() !== 1) {
throw new DomainException('Ürün bulunamadı veya stok yetersiz.');
}
$insert = $pdo->prepare(
'INSERT INTO orders (product_id, quantity, status)
VALUES (:product_id, :quantity, :status)'
);
$insert->execute([
'product_id' => $productId,
'quantity' => $quantity,
'status' => 'pending',
]);
$orderId = (int) $pdo->lastInsertId();
$pdo->commit();
return $orderId;
} catch (Throwable $exception) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $exception;
}
}
Sorgu, ürünü yalnızca stock >= :required koşulu sağlanıyorsa günceller. Aynı satırı değiştirmeye çalışan işlemler InnoDB tarafından koordine edilir. İlk işlem son stoğu düşürüp tamamladığında, sonraki işlem koşulu yeniden değerlendirdiğinde yeterli stok bulamaz ve güncellenen satır sayısı sıfır olur.
Burada miktarın sıfırdan büyük olması önemli: Böylece başarılı bir güncelleme gerçekten stok değerini değiştirir. rowCount() sonucunu da bu senaryo için anlamlı biçimde kullanabiliriz.
Sipariş eklenirken hata çıkarsa rollBack() ile stok güncellemesi de geri alınır. Hazırlanmış sorgulardaki parametreler kullanıcı verilerini SQL metnine doğrudan eklemekten kaçınmamıza yardımcı olur; bunun kapsamı ve kullanımı için PHP PDO dokümantasyonuna bakabilirsin.
Bağlantı ve örnek kullanım
Bağlantı bilgilerini kaynak koda yazmak yerine ortam değişkenlerinden alalım:
<?php
$pdo = new PDO(
getenv('DB_DSN'), // Örn: mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4
getenv('DB_USER'),
getenv('DB_PASS'),
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
try {
$orderId = createOrder($pdo, productId: 1, quantity: 1);
echo 'Sipariş oluşturuldu: '.$orderId;
} catch (DomainException $exception) {
echo 'Ürün bulunamadı veya stok yetersiz.';
}
Bu çağrı, önceki fonksiyonla aynı dosyada veya onun yüklendiği bir ortamda çalıştırılmalıdır. PDO MySQL sürücüsünün kurulu olması ve ortam değişkenlerinin tanımlanması gerekir. Üretimde beklenmeyen hata ayrıntılarını ziyaretçiye göstermeyip uygun şekilde kaydetmek de önemlidir.
Gerçek projede neleri ayrıca düşünmeli?
Çift istekler: Bu yaklaşım stok eksiye düşmesin diye koruma sağlar; aynı sipariş isteğinin iki kez gönderilmesini tek başına engellemez. Aynı işlem için benzersiz bir istek anahtarı ve veritabanında benzersizlik kuralı düşünülmelidir.
Ödeme ve dış servisler: Banka API’sini veya bir kargo servisini transaction açıkken uzun süre beklemek, kilitlerin uzun süre tutulmasına yol açabilir. Sipariş ve ödeme durumlarını ayrı ele almak; tutarlı, yeniden denenebilir bir iş akışı tasarlamak gerekir.
Birden fazla ürün: Her ürün için güncelleme aynı transaction içinde yapılmalı. Ürünleri her işlemde tutarlı bir sırayla güncellemek kilitlenme riskini azaltabilir; yine de deadlock gibi durumları izleyip kontrollü yeniden denemeyi planlamak gerekir.
Stok rezervasyonu: Ödeme beklerken stok ayırıyorsan, süresi dolan veya başarısız olan rezervasyonların nasıl geri bırakılacağını da tanımlamalısın.
Nasıl test edebilirsin?
Test veritabanında bir ürünün stoğunu 1 yap ve iki ayrı bağlantıdan aynı anda birer adetlik sipariş dene. Beklentin bir siparişin başarılı olması, diğerinin stok yetersiz cevabı alması ve sonunda stoğun 0 olmasıdır. Ardından sipariş kaydının oluşturulmasını kontrollü biçimde başarısız kılıp stok düşüşünün geri alındığını doğrula.
Kilitlerin tam olarak neyi kapsadığı kullanılan sorguya, indekse ve izolasyon seviyesine bağlıdır. Daha karmaşık iş akışlarında MySQL’in InnoDB kilitleme dokümantasyonunu incelemek faydalı olur.
Küçük bir stok kontrolü gibi görünen bu konu, aslında güvenilir bir sipariş akışının temel parçalarından biri. Önce sınırları doğru çizmek, ardından eş zamanlı istekleri test etmek sonradan oluşabilecek zor sorunları azaltır.
BİRLİKTE ÖĞRENELİM
Yorumlar
Henüz yorum yok. Düşünceni paylaşarak sohbeti başlatabilirsin.
Bir Yorum Bırak.
Katkılarını ve deneyimlerini duymak isterim. Yorumlar yayınlanmadan önce incelenir.