Node.js — Transaction, xatolarni boshqarish
Bank o'tkazmasi: ikki UPDATE bir vaqtda bajarilishi kerak. Pool dan oddiy pool.query() ishlatilsa — har so'rov alohida ulanishda, tranzaksiya imkonsiz. Tranzaksiya uchun bitta ulanishni ushlab turish kerak.
Pool dan client olish
const client = await pool.connect(); // bitta ulanish olish
try {
// Bu ulanishda barcha so'rovlar
await client.query('BEGIN');
// ...
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release(); // ulanishni hovuzga qaytarish
}
finally — xato bo'lsa ham, bo'lmasa ham release() chaqiriladi. Aks holda ulanish bloklanib qoladi.
Bank o'tkazmasi — to'liq misol
async function otkazma(manbaId, maqsadId, summa) {
const client = await pool.connect();
try {
await client.query('BEGIN');
// Manba hisobdan yetarli mablag' bormi?
const { rows } = await client.query(
'SELECT balans FROM hisoblar WHERE id = $1 FOR UPDATE',
[manbaId]
);
if (!rows[0]) throw new Error('Manba hisobi topilmadi');
if (rows[0].balans < summa) throw new Error("Mablag' yetarli emas");
// Mablag' chiqarish
await client.query(
'UPDATE hisoblar SET balans = balans - $1 WHERE id = $2',
[summa, manbaId]
);
// Mablag' kiritish
await client.query(
'UPDATE hisoblar SET balans = balans + $1 WHERE id = $2',
[summa, maqsadId]
);
// Tarix yozish
await client.query(
'INSERT INTO otkazmalar (manba_id, maqsad_id, summa) VALUES ($1, $2, $3)',
[manbaId, maqsadId, summa]
);
await client.query('COMMIT');
return { muvaffaqiyat: true };
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
Xato kodlari
PostgreSQL xato kodlari — aniq xatolikni aniqlash uchun:
try {
await pool.query('INSERT INTO foydalanuvchilar (email) VALUES ($1)', [email]);
} catch (err) {
switch (err.code) {
case '23505': // unique_violation
throw new Error('Bu email allaqachon ro\'yxatdan o\'tgan');
case '23503': // foreign_key_violation
throw new Error('Bog\'liq yozuv topilmadi');
case '23502': // not_null_violation
throw new Error(`${err.column} maydoni bo'sh bo'lmasligi kerak`);
case '22P02': // invalid_text_representation
throw new Error('Noto\'g\'ri format');
default:
throw err;
}
}
Qayta urinish — Serialization Failure
Serializable transaction da ziddiyat bo'lsa — qayta urinish:
async function serializable(fn) {
for (let i = 0; i < 3; i++) {
const client = await pool.connect();
try {
await client.query('BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE');
const natija = await fn(client);
await client.query('COMMIT');
return natija;
} catch (err) {
await client.query('ROLLBACK');
if (err.code === '40001' && i < 2) continue; // serialization_failure — qayta urin
throw err;
} finally {
client.release();
}
}
}
// Ishlatish
await serializable(async (client) => {
await client.query('UPDATE ...');
await client.query('INSERT ...');
});
Helper funksiya — transaction wrapper
async function withTransaction(fn) {
const client = await pool.connect();
try {
await client.query('BEGIN');
const result = await fn(client);
await client.query('COMMIT');
return result;
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
// Ishlatish
const user = await withTransaction(async (client) => {
const { rows: [user] } = await client.query(
'INSERT INTO foydalanuvchilar (ism) VALUES ($1) RETURNING *', ['Jasur']
);
await client.query(
'INSERT INTO profillar (foydalanuvchi_id) VALUES ($1)', [user.id]
);
return user;
});
Xulosa
// Transaction pattern
const client = await pool.connect();
try {
await client.query('BEGIN');
// so'rovlar...
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release(); // har doim!
}
// Xato kodlari
'23505' — unique violation
'23503' — foreign key violation
'23502' — not null violation
'40001' — serialization failure (qayta urin)
Keyingi dars
64-dars: Real loyiha — to'liq DB schema loyihalash va yozish