В каких случаях стоит применять транзакцию?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Transakcije u bazi podataka - Kada ih koristiti
Transakcija je sekvenca SQL operacija koja se izvrsava atomski - sve se izvrse ili se sve poniste. ACID principi garantuju konzistentnost podataka.
Sta su transakcije?
Transakcija je logicka jedinica rada koja:
- Atomska - sve ili nista
- Konzistentna - baza ide iz jednog stabilnog stanja u drugo
- Izolirana - ne vidljive druge transakcije dok se ne kompleta
- Trajno - kad je commit, podaci ostaju trajno
Primjer bez transakcije (LOSE)
class TransferMoney {
public function transfer(int $fromUserId, int $toUserId, float $amount): bool {
// Prvo: oduzmi novac od prvog korisnika
$result1 = $this->db->query(
"UPDATE users SET balance = balance - ? WHERE id = ?",
[$amount, $fromUserId]
);
if (!$result1) return false;
// Sada je novac oduzan ali...
// Drugo: dodaj drugom korisniku
// AKO OVO FALI, NOVAC JE IZGUBLJEN!
$result2 = $this->db->query(
"UPDATE users SET balance = balance + ? WHERE id = ?",
[$amount, $toUserId]
);
// Ako drugi query padne, prvi je vec izvrshen
return $result2;
}
}
Resenje sa transakcijom (DOBRO)
class TransferMoney {
public function transfer(int $fromUserId, int $toUserId, float $amount): bool {
try {
$this->db->beginTransaction();
// Atomska operacija - ili sve ili nista
$this->db->query(
"UPDATE users SET balance = balance - ? WHERE id = ?",
[$amount, $fromUserId]
);
$this->db->query(
"UPDATE users SET balance = balance + ? WHERE id = ?",
[$amount, $toUserId]
);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollBack();
return false;
}
}
}
Slucajevi kada OBAVEZNO trebate transakcije
1. Novčane operacije
Transfer sredstava - ako se sredstva oduzmu ali se ne dodaju drugoj strani, klijent izgubi novac.
function transferFunds(Account $from, Account $to, Money $amount): void {
$this->db->beginTransaction();
try {
$from->withdraw($amount);
$to->deposit($amount);
$this->db->commit();
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
2. Vise povezanih redaka
Kada promenite vise redaka koji moraju biti konzistentni:
function createOrder(User $user, array $items): Order {
$this->db->beginTransaction();
try {
// Kreiraj red u orders tabeli
$order = Order::create(['user_id' => $user->id]);
// Dodaj sve stavke
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
'product_id' => $item['product_id'],
'quantity' => $item['quantity']
]);
}
// Oduzmi proizvode iz skladista
foreach ($items as $item) {
Stock::query()
->where('product_id', $item['product_id'])
->decrement('quantity', $item['quantity']);
}
$this->db->commit();
return $order;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
3. Brisanje sa foreing key ogranicenjima
function deleteUserWithData(User $user): void {
$this->db->beginTransaction();
try {
// Sve ili nista - komentari se brisu sa korisnikom
Comment::where('user_id', $user->id)->delete();
Post::where('user_id', $user->id)->delete();
$user->delete();
$this->db->commit();
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
4. Kompleksne poslovne operacije
function completeSubscription(Subscription $sub): void {
$this->db->beginTransaction();
try {
// Sve ove operacije moraju biti atomske
$sub->update(['status' => 'active']);
// Kreiraj prvo naplacivanje
Invoice::create([
'subscription_id' => $sub->id,
'amount' => $sub->price
]);
// Loguj aktivnost
ActivityLog::create([
'user_id' => $sub->user_id,
'action' => 'subscription_activated'
]);
$this->db->commit();
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
Slucajevi kada NE trebate transakcije
1. Jednostavni read operacije
// Ne trebate transakcija za citanje
var_dump($user = User::find(1));
2. Izolirana write operacija
// Ako je samo jedan UPDATE i nema zavisnih operacija
$user->update(['last_login' => now()]);
3. Performance-osetljive operacije sa puno redaka
// Brisanje miliona redaka u jednoj transakciji moze zakljucati bazu
// Bolje je batch po 1000 redaka
for ($i = 0; $i < 1000000; $i += 1000) {
Log::query()->limit(1000)->delete();
}
Best Practices
1. Kratkotrajne transakcije
// LOSE - dugacka transakcija
$this->db->beginTransaction();
sleep(10); // Dugacak proces
$this->db->commit();
// DOBRO - kratka transakcija
$data = $this->expensiveCalculation(); // Van transakcije
$this->db->beginTransaction();
$this->saveData($data);
$this->db->commit();
2. Izbjegavanje deadlockova
// Uvijek zaključaj u istom redoslijedu
$this->db->beginTransaction();
$account1 = Account::query()->where('id', 1)->lockForUpdate()->first();
$account2 = Account::query()->where('id', 2)->lockForUpdate()->first();
$this->db->commit();
3. Ispravna rukovanja greskom
function operation(): void {
try {
$this->db->beginTransaction();
// ... operacije ...
$this->db->commit();
} catch (Exception $e) {
$this->db->rollBack();
Log::error('Transaction failed', ['error' => $e->getMessage()]);
throw $e; // Proslijedi dalje ako je potrebno
}
}
Nivoi izolacije
- READ UNCOMMITTED - Skazuje sve, cak i neupoćene promene
- READ COMMITTED - Default, čita samo potvrđene podatke
- REPEATABLE READ - Ista čitanja daju iste rezultate
- SERIALIZABLE - Potpuno izolovane, spora
$this->db->query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
$this->db->beginTransaction();
Zakljucak: Transakcije su OBAVEZNE za sve operacije koje zavise jedne od drugih ili gdje konzistentnost podataka nije opcija.