Node.js — Transaction, xatolarni boshqarish

~20 daq · PostgreSQL · PostgreSQL va Node.js

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