View : 206

08/05/2026 06:52am

JS2GO EP.45 Transaction Management and ACID in Go vs JavaScript

JS2GO EP.45 Transaction Management and ACID in Go vs JavaScript

#Transactions

#JavaScript

#ACID

#Golang

#Go

#Node.js

A transaction is a group of operations that must all succeed together or fail together.

 

They are essential for any system that requires 100% data accuracy, such as:

  • Deducting balance + writing transaction logs + updating stock
  • Transferring money between accounts
  • Moving data from “Order” → “Shipment”
  • Updating several tables in one workflow

 

If any single operation fails, the system must ROLLBACK everything otherwise the data becomes inconsistent, causing serious long-term issues.

 

In this episode, you will learn:

✔ ACID Properties
✔ How to handle Deadlocks
✔ Go Transaction Patterns (pgx + GORM)
✔ Node.js Transaction Patterns (pg + PostgreSQL)
✔ SQL Transactions vs Business Transactions
✔ Production Best Practices

 

⭐ 1) ACID Properties The Foundation of Reliable Systems

 

ACID represents the 4 essential guarantees of any safe and consistent transaction.

PropertyMeaning
AtomicityAll operations succeed or none do
ConsistencyData remains valid and follows constraints after commit
IsolationTransactions should not interfere with one another
DurabilityOnce committed, data is permanently stored

 

💡 Example: Transfer 1,000 THB

  1. Deduct from Account A
  2. Add to Account B
  3. Write log

 

If step #2 fails → rollback everything. This is why ACID is critical for finance, e-commerce, logistics, and enterprise systems.

 

⭐ 2) Universal SQL Transaction Flow

 

Works with PostgreSQL and MySQL:

BEGIN;
-- operations
COMMIT;
-- or
ROLLBACK;

 

Simple but extremely powerful.

 

⭐ 3) Isolation Levels The Most Overlooked Setting

 

SQL defines 4 isolation levels:

LevelPreventsNotes
Read UncommittedRarely used
Read CommittedDirty ReadsDefault in PostgreSQL
Repeatable ReadNon-repeatable ReadsSafer, more strict
SerializablePhantom ReadsSafest but more deadlocks

 

PostgreSQL’s default (READ COMMITTED) works for 90% of production workloads.

 

⭐ 4) What Is a Deadlock?

 

A deadlock happens when two transactions wait for each other forever.

T1T2
Locks Row A 
 Locks Row B
Tries to lock Row B → waits 
 Tries to lock Row A → waits

 

Both transactions freeze.

 

✔ How to fix deadlocks:

  • Detect error code & retry
  • Apply consistent lock ordering
  • Use SELECT ... FOR UPDATE carefully
  • Set statement timeout

 

⭐ 5) SQL Transaction vs Business Transaction

 

SQL Transaction

Happens within one database:

  • Money transfer
  • Update multiple fields
  • CRUD operations
  • Stock reservation

 

Business Transaction

Involves multiple services, e.g.:

  • Payment
  • Inventory
  • Shipping
  • Email / Notification

 

This requires advanced patterns:

✔ Saga Pattern
✔ Outbox Pattern
✔ Event-driven architecture

 

In EP.45 we focus mainly on SQL transactions.

 

⭐ 6) Transactions in Go (pgx)

 

pgx is the fastest and most production-ready PostgreSQL driver for Go.

 

🔹 Basic Transaction (pgx)

tx, err := DB.Begin(ctx)
if err != nil {
    return err
}
defer tx.Rollback(ctx)

_, err = tx.Exec(ctx, "UPDATE users SET ...")
if err != nil {
    return err
}

return tx.Commit(ctx)

 

🔹 Full Transaction with Deadlock Retry (Recommended)

func Transfer(ctx context.Context, from, to string, amount int) error {
    for retry := 0; retry < 3; retry++ {

        tx, err := DB.Begin(ctx)
        if err != nil {
            return err
        }
        defer tx.Rollback(ctx)

        // Step 1: Deduct
        _, err = tx.Exec(ctx,
            "UPDATE accounts SET balance = balance - $1 WHERE id=$2",
            amount, from,
        )
        if err != nil {
            continue // Retry on deadlock
        }

        // Step 2: Add
        _, err = tx.Exec(ctx,
            "UPDATE accounts SET balance = balance + $1 WHERE id=$2",
            amount, to,
        )
        if err != nil {
            continue
        }

        if err := tx.Commit(ctx); err == nil {
            return nil
        }
    }
    return errors.New("transaction failed after retries")
}

 

This is the gold standard for mission-critical systems.

 

⭐ 7) Transactions in Go (GORM)

 

GORM makes transactions extremely easy:

func Transfer(db *gorm.DB, from, to string, amount int) error {
    return db.Transaction(func(tx *gorm.DB) error {

        if err := tx.Model(&Account{}).
            Where("id = ?", from).
            Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
            return err
        }

        if err := tx.Model(&Account{}).
            Where("id = ?", to).
            Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
            return err
        }

        return nil
    })
}

 

GORM automatically handles commit/rollback.

 

⭐ 8) Transactions in Node.js (pg)

 

🔹 Basic Transaction

const client = await pool.connect();

try {
  await client.query("BEGIN");

  await client.query(
    "UPDATE accounts SET balance = balance - $1 WHERE id=$2",
    [1000, "A"]
  );

  await client.query(
    "UPDATE accounts SET balance = balance + $1 WHERE id=$2",
    [1000, "B"]
  );

  await client.query("COMMIT");
} catch (err) {
  await client.query("ROLLBACK");
  throw err;
} finally {
  client.release();
}

 

🔹 Transaction with Deadlock Retry (Recommended)

async function transfer(from, to, amount) {
  for (let attempt = 0; attempt < 3; attempt++) {
    const client = await pool.connect();
    try {
      await client.query("BEGIN");

      await client.query(
        "UPDATE accounts SET balance = balance - $1 WHERE id=$2",
        [amount, from]
      );

      await client.query(
        "UPDATE accounts SET balance = balance + $1 WHERE id=$2",
        [amount, to]
      );

      await client.query("COMMIT");
      return;
    } catch (err) {
      await client.query("ROLLBACK");

      if (err.code === "40P01") continue; // Deadlock
      throw err;
    } finally {
      client.release();
    }
  }
  throw new Error("transaction failed after retries");
}

 

Equivalent to Go’s retry pattern.

 

⭐ 9) Transaction Scopes

 

ScopeWhen to Use
Single QueryNo transaction needed
Simple multi-step logicUse SQL transaction
Complex domain logicEncapsulate in a service function
Distributed workflowUse Saga / Outbox

 

⭐ 10) Production Best Practices

 

✔ Set statement timeout — prevent long-running locks
✔ Detect & retry deadlocks (error code 40P01)
✔ Keep transactions short-lived
✔ Log commit/rollback
✔ Implement idempotency for retryable operations
✔ Avoid unnecessary SELECT FOR UPDATE

 


 

📌 Summary

 

Transactions protect your system from inconsistent data and catastrophic errors.

 

In this episode, you learned:

✔ ACID & Isolation Levels
✔ Deadlock Detection & Retry
✔ Go (pgx/GORM) Transaction Patterns
✔ Node.js (pg) Transaction Patterns
✔ Production Best Practices

 

🔵 EP.46 Middleware & Modular Architecture in Go and Node.js

 

In the next episode, we will explore how to build backend systems that are clean, maintainable, and production-ready using both Go and Node.js.

 

  • Middleware Fundamentals
  • Service / Repository Pattern
  • Production-Ready Project Structure