Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
YouTube Security VideosNutanix advances legacy and AI app management with AMD(01.10.2026 um 16:00 Uhr)
•
YouTube Security VideosPC-WELT: 32 TB SSD-Speicher in der HMX 6!(01.10.2026 um 16:15 Uhr)
••••
Videos & KonferenzenPC-WELT: 32 TB SSD-Speicher in der HMX 6!(01.10.2026 um 16:15 Uhr)
••
Sicherheitslücken (CVE)USN-8857-1: KCoreAddons vulnerability(01.10.2026 um 12:48 Uhr)
•••
YouTube Security VideosNutanix advances legacy and AI app management with AMD(01.10.2026 um 16:00 Uhr)
•
YouTube Security VideosPC-WELT: 32 TB SSD-Speicher in der HMX 6!(01.10.2026 um 16:15 Uhr)
••••
Videos & KonferenzenPC-WELT: 32 TB SSD-Speicher in der HMX 6!(01.10.2026 um 16:15 Uhr)
••
Sicherheitslücken (CVE)USN-8857-1: KCoreAddons vulnerability(01.10.2026 um 12:48 Uhr)
•••
Intelligence View
⚡ tsecurity.de Intelligence

Go Concurrency with Database Transactions

Many of us have shipped features that passed every test, only to watch them break in production. That happened to me a few months ago when I tried to speed…

Beitrag
0
Seite
0
↗ Quelle (dev.to)
Social ReaktionenReagiere als Erste:r — dein Feedback zählt!

Many of us have shipped features that passed every test, only to watch them break in production. That happened to me a few months ago when I tried to speed things up with concurrency, forgetting one important constraint: database transactions aren’t thread-safe, so you can't safely share a transaction across goroutines. Let's look at this code




func (s *service) ProcessOrder(ctx context.Context, orderID int) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()

var order *Order
var inventory *Inventory

errG, grpCtx := errgroup.WithContext(ctx)

errG.Go(func() error {
o, err := s.repo.GetOrder(grpCtx, tx, orderID)
if err != nil {
return err
}

order = o
return nil
})

errG.Go(func() error {
i, err := s.repo.GetInventory(grpCtx, tx, orderID)
if err != nil {
return err
}

inventory = i
return nil
})

if err := errG.Wait(); err != nil {
return err
}

if inventory.Stock < order.Quantity {
return ErrInsufficientStock
}

if err := s.repo.DeductStock(ctx, tx, order.ProductID, order.Quantity); err != nil {
return err
}

if err := s.repo.MarkOrderShipped(ctx, tx, orderID); err != nil {
return err
}

return tx.Commit()
}






I spun up two goroutines to fetch the order and inventory concurrently. It seemed like the right thing to do—they were independent reads, why not? For a while, everything appeared fine: several days in staging, hundreds of orders, zero visible issues. But a few hours into production, orders stopped processing. The only clue was an error that made no sense at first:




pq: unexpected Parse response 'C'






And right alongside that, another symptom appeared: stuck orders. Users would place orders successfully, but their status never updated. The operation looked successful externally, yet the database state didn't move. It was a silent mid-transaction failure.



In Go, a *sql.Tx is bound to single database connection and that connection is a single TCP socket which isn't thread-safe. When two goroutines write to the same socket simultaneously, the PostgreSQL wire protocol messages get interleaved and the stream becomes corrupted.

With two goroutines racing, the protocol message sequences overlap and goroutine A can end up receiving the parse response intended for goroutine B, so the driver loses track of which response belongs to which query, eventually triggering the error we saw above.



Transaction wire protocol corruption



Why didn't it fail in the staging? That's the nature of Heisenbugs, they seem to disappear when you look for them. Race conditions are probabilistic, this one was there the whole time. Staging had low traffic, so the two goroutines often finished before they could overlap. And if you're wondering: no, go test -race won't catch this. The race detector instruments Go memory, not TCP socket I/O. The corruption happened at the wire level, outside Go's visibility.



The fix was simple: sequential queries, no shared transaction across goroutines.




func (s *service) ProcessOrder(ctx context.Context, orderID int) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()

order, err := s.repo.GetOrder(ctx, tx, orderID)
if err != nil {
return err
}

inventory, err := s.repo.GetInventory(ctx, tx, orderID)
if err != nil {
return err
}

if inventory.Stock < order.Quantity {
return ErrInsufficientStock
}

if err := s.repo.DeductStock(ctx, tx, order.ProductID, order.Quantity); err != nil {
return err
}

if err := s.repo.MarkOrderShipped(ctx, tx, orderID); err != nil {
return err
}

return tx.Commit()
}






If you need concurrent database work, do it before starting the transaction, or use separate connections. But once you call BeginTx, that connection belongs to one goroutine until Commit or Rollback.

🔍 CTI & Forensik

Cyber Threat Intelligence & Forensik

Bedrohungsgraph · ATT&CK-Mapping · Exploit-Belege
CTI Threat Relationship Graph
Akteure · Techniken · Beziehungen
3 Knoten · 2 Relationen
CVE / Incident Threat Actor Software MITRE ATT&CK CWE Weakness IoC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Go Concurrency with Database Transactions

Thematisch verwandte Begriffe: Concurrency, with, Database, Transactions · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag