今日已更新 412 条资讯 | 累计 19972 条内容
关于我们

标签:#transaction

找到 6 篇相关文章

AI 资讯

Row Lock — FOR UPDATE

FOR UPDATE: pessimistic row lock để chặn lost update, và cái giá deadlock khi không lock theo thứ tự SELECT ... FOR UPDATE là cách rõ ràng nhất để nói với Postgres "tao sẽ sửa row này, đừng cho ai khác đụng vào cho tới khi tao commit". Nó là một row-level lock thật sự — khác SELECT thường (chỉ chụp snapshot MVCC, không ngăn ai update song song). Lý do dev gặp nó trong việc thật là class bug lost update : hai transaction cùng đọc một row, cùng tính giá trị mới dựa trên giá trị đọc được, rồi cùng UPDATE — bản ghi cuối đè bản trước, một nửa thay đổi biến mất không log lỗi gì. FOR UPDATE ép hai bên xếp hàng tại bước đọc, một bên đợi bên kia commit rồi tự đọc lại bản mới. Đổi lại, nếu nhiều code path khoá nhiều row theo thứ tự khác nhau, Postgres sẽ bắn ERROR: deadlock detected và một bên transaction bay theo. Cơ chế hoạt động Khi một transaction chạy SELECT ... FOR UPDATE , Postgres không ghi row lock vào lock table chính (như cách nó làm với relation-level lock). Thay vào đó, nó ghi xid của transaction hiện tại vào xmax của chính tuple đó trên heap, kèm cờ infomask đánh dấu "đây là lock chứ chưa phải delete". Hệ quả: ôm row lock cho hàng triệu row gần như không tốn shared memory. Khi một transaction khác chạm cùng row (qua UPDATE , DELETE , hay một SELECT ... FOR UPDATE nữa), nó đọc xmax , thấy transaction kia còn sống, và đăng ký một heavyweight lock kiểu transactionid trong pg_locks để đợi xid đó kết thúc. Đó là cơ chế "đợi xid" lộ ra qua wait_event = 'transactionid' ở pg_stat_activity . Khi bên giữ COMMIT hoặc ROLLBACK , bên đợi được đánh thức, đọc lại tuple (visibility check theo isolation level), rồi mới chạy tiếp. -- Session A BEGIN ; SELECT id , balance FROM accounts WHERE id = 42 FOR UPDATE ; -- giữ row lock trên id=42, chưa commit -- Session B (terminal khác) BEGIN ; UPDATE accounts SET balance = balance - 100 WHERE id = 42 ; -- treo, đợi xid của Session A FOR UPDATE có bốn biến thể, mạnh dần ngược lại: FOR KEY SHARE (yếu nhất, chỉ chặn thay đổi key — đây là l

2026-07-07 原文 →
AI 资讯

Transaction State — Idle in Transaction

Idle in transaction: connection ngồi không nhưng vẫn giữ snapshot, VACUUM đứng hình và bảng phình idle in transaction là trạng thái mà pg_stat_activity.state đặt cho một connection đã BEGIN , đã chạy xong statement gần nhất, và đang ngồi đợi statement kế tiếp hoặc COMMIT / ROLLBACK . Trên giấy nó "rảnh"; trong thực tế nó vẫn cầm một snapshot, vẫn giữ backend_xmin , vẫn ôm mọi lock đã claim từ đầu transaction. Một connection trong state này chỉ vài chục giây không nguy hiểm; cùng connection đó ngồi vài giờ trên cluster có write traffic là kịch bản kinh điển khiến bảng update-nóng bloat, autovacuum đứng yên, replica conflict, và DBA on-call bị page lúc 3 giờ sáng. Đây không phải bug Postgres mà là application code path đã lỡ rời transaction mở rồi đi làm việc khác (đợi user input, gọi HTTP ra ngoài, ngủ trong queue worker) — Postgres chỉ trung thực phản chiếu lại. Cơ chế hoạt động Khi một connection gửi BEGIN , Postgres ghi nhận transaction nhưng chưa cấp xid (chỉ cấp khi câu lệnh ghi đầu tiên xuất hiện) và lập tức gán cho backend này một snapshot : tập hợp các xid mà transaction sẽ coi là visible. Snapshot này được phản chiếu ra cột backend_xmin trong pg_stat_activity . Ngay sau BEGIN , state của connection là idle in transaction . Mỗi câu lệnh sau đó đẩy connection sang active trong lúc chạy, rồi trả về idle in transaction ngay khi statement hoàn tất — và nó ở đó vô thời hạn , cho tới khi client gửi câu lệnh kế tiếp, gửi COMMIT / ROLLBACK , hoặc connection bị terminate. -- Session A BEGIN ; -- pg_stat_activity.state = 'idle in transaction' -- pg_stat_activity.backend_xmin = <xid horizon ngay tại thời điểm BEGIN> SELECT count ( * ) FROM orders ; -- trong lúc chạy: state = 'active' -- xong: state = 'idle in transaction', snapshot KHÔNG bị nhả -- (client đi gọi HTTP ngoài, hoặc đơn giản là quên gửi COMMIT) -- nhiều giờ trôi qua. state vẫn 'idle in transaction'. -- backend_xmin vẫn pin xmin horizon của cluster. Điểm cốt tử là backend_xmin : VACUUM (và autovacuum ) chỉ đ

2026-07-07 原文 →
AI 资讯

BEGIN/COMMIT — Transaction Lifecycle

Transaction lifecycle trong Postgres: BEGIN mở state machine, COMMIT đóng — quên đóng là dò mìn Một transaction trong Postgres không phải chỉ là cặp BEGIN ... COMMIT cú pháp; nó là một state machine sống cùng connection. BEGIN đẩy connection từ idle sang active , mỗi statement kết thúc đẩy nó về idle in transaction đợi statement kế tiếp, một statement lỗi đẩy sang idle in transaction (aborted) , và chỉ COMMIT / ROLLBACK mới trả connection về idle . Dev gặp lifecycle này trong việc thật không phải vì cú pháp khó mà vì một BEGIN quên COMMIT trong một code path lỗi: connection nằm trong pool ở idle in transaction vô thời hạn, giữ snapshot và lock, chặn autovacuum , kéo lock chain, làm bảng update-nóng bloat dần rồi cả service chậm chết. Cơ chế hoạt động Mặc định mỗi connection ở autocommit mode : mỗi statement là một transaction tự đóng. BEGIN (hoặc START TRANSACTION ) tắt autocommit cho tới khi gặp COMMIT / ROLLBACK . Trong khoảng đó connection có một xid (cấp khi cần ghi) và một snapshot, và lifecycle của nó đi qua các trạng thái mà Postgres phơi ra trong pg_stat_activity.state : idle — connection mở, không có transaction nào đang chạy. active — đang thực thi một statement (kể cả ngoài transaction block). idle in transaction — đang trong transaction block, vừa chạy xong một statement, đợi statement kế tiếp hoặc COMMIT / ROLLBACK . idle in transaction (aborted) — đang trong transaction, một statement đã ném lỗi, mọi statement tiếp theo trả ERROR: current transaction is aborted, commands ignored until end of transaction block cho tới khi ROLLBACK . fastpath function call / disabled — ít gặp, không phải mục tiêu của bài này. -- t0: state = 'idle' BEGIN ; -- t1: state = 'idle in transaction' (vừa thực thi xong BEGIN, đợi statement kế) INSERT INTO orders ( user_id , total ) VALUES ( 42 , 100 ); -- trong lúc chạy: state = 'active' -- sau khi statement xong: state = 'idle in transaction' lại INSERT INTO orders ( user_id , total ) VALUES ( NULL , 100 ); -- ERROR: null value

2026-07-07 原文 →
AI 资讯

Isolation Level — Read Committed

Read Committed: snapshot mỗi statement, và vì sao hai SELECT trong cùng transaction có thể trả khác nhau READ COMMITTED là isolation level mặc định của PostgreSQL, và là level mà phần lớn workload OLTP đang chạy mà không biết. Khác với mô hình "transaction lấy một snapshot rồi giữ nguyên" mà nhiều dev tưởng tượng từ MVCC, ở Read Committed mỗi statement lấy một snapshot mới tại thời điểm statement bắt đầu , không phải tại thời điểm BEGIN . Hậu quả thực tế: hai SELECT liên tiếp trong cùng một transaction có thể trả về dữ liệu khác nhau nếu giữa hai lần đó có transaction khác commit. Đây là non-repeatable read — đúng spec của Read Committed, không phải bug — và là nguồn của một class lỗi rất hay gặp: code đọc một giá trị, ra quyết định, rồi cập nhật dựa trên giá trị đã đọc, trong khi giá trị thực tế đã thay đổi. Cơ chế hoạt động Một transaction ở Read Committed không có transaction-level snapshot . Khi mỗi statement (mỗi SELECT , UPDATE , DELETE , INSERT ... SELECT ...) bắt đầu thực thi, backend lấy một snapshot mới gồm xmin , xmax và xip list — chính cái snapshot quyết định row version nào "visible" theo MVCC. Statement chỉ thấy: row có xmin đã commit trước thời điểm statement bắt đầu , và xmax chưa tồn tại hoặc thuộc một transaction chưa commit / đã abort. Ngay sau khi statement kết thúc, snapshot đó bị bỏ. Statement kế tiếp lấy snapshot mới — nếu trong khoảng giữa có transaction khác commit, statement này sẽ thấy dữ liệu mới đó. -- T1 BEGIN ; -- KHÔNG lấy snapshot ở đây SELECT balance FROM accounts WHERE id = 1 ; -- snapshot S1 -> trả 1000 -- ... T2 chạy: UPDATE accounts SET balance=500 WHERE id=1; COMMIT; SELECT balance FROM accounts WHERE id = 1 ; -- snapshot S2 -> trả 500 COMMIT ; Với UPDATE / DELETE / SELECT ... FOR UPDATE / FOR NO KEY UPDATE / FOR SHARE , Read Committed làm thêm một bước đặc biệt mà SELECT thường không làm: nếu target row bị một transaction khác đang lock (chưa commit), statement đợi transaction đó kết thúc. Khi unblock: nếu transaction kia ROL

2026-07-07 原文 →
AI 资讯

Your @EventListener Fires Before the Transaction Commits⚙️

Your domain event fires. Your notification service queries the DB for the entity that just got saved. It finds nothing. You add a log line. It starts working. You remove the log. It breaks again. That's not a race condition. That's @EventListener . What's actually happening Spring's @EventListener fires synchronously, inside the calling thread, before the transaction commits. The DB row exists in Hibernate's session — but it hasn't been flushed and committed yet. Other connections, including the one your listener opens when it calls findById , can't see it. The log statement "fixes" it because the delay gives Hibernate time to flush. Remove the log, the flush doesn't happen in time, and you're back to an empty Optional . Here's the broken setup: @Component public class OrderEventListener { @EventListener // fires MID-TRANSACTION, before commit public void onOrderCreated ( OrderCreatedEvent event ) { // Transaction not committed yet. // Other DB connections see nothing. Order order = orderRepository . findById ( event . getOrderId ()) . orElseThrow (); // ← throws here, row doesn't exist yet notificationService . notifyCustomer ( order ); } } The obvious fix and what it costs you Spring ships @TransactionalEventListener for exactly this. Set phase = TransactionPhase.AFTER_COMMIT and the listener fires after the transaction commits. The row is visible. findById returns the order. Problem solved. @Component public class OrderEventListener { @TransactionalEventListener ( phase = TransactionPhase . AFTER_COMMIT ) public void onOrderCreated ( OrderCreatedEvent event ) { // Transaction committed. All connections see the row. Order order = orderRepository . findById ( event . getOrderId ()) . orElseThrow (); // ← works fine notificationService . notifyCustomer ( order ); } } But the trade-off is real. Your listener is now decoupled from the transaction. If the listener fails — notification service is down, the email throws, the external API times out — the transaction alrea

2026-06-25 原文 →
AI 资讯

What Happens When a Database Operation Fails Midway? NestJS Transactions to the Rescue

Imagine a simple money transfer scenario. John sends money to his friend Sarah. The system successfully deducts money from John's account, but before it can credit Sarah's account, the application crashes. Without proper safeguards, John's money would disappear from the system, creating inconsistent and unreliable financial records. To prevent this type of problem, database transactions are used. Transactions ensure that a group of related database operations either complete successfully together or fail together. If any part of the process encounters an error, all changes are reverted, ensuring that the database remains consistent. Transactions make database operations atomic. Atomicity means that all operations inside a transaction are treated as a single unit of work. Either every operation succeeds and is committed to the database, or all operations fail and are rolled back. Partial updates are never permanently stored. A database transaction is a group of one or more database operations executed as a single unit. Either all operations succeed together or all operations fail together. This guarantees database consistency even if an application crashes, a network failure occurs, or an unexpected error is encountered during execution. It is important to understand that a transaction is not simply a single database query. While individual queries such as save, update, or delete interact with the database, a transaction wraps multiple queries inside a controlled all-or-nothing boundary. This prevents partial updates and ensures data integrity throughout the process. Prerequisites Before starting, make sure you have the following: Basic knowledge of NestJS Basic understanding of TypeORM Basic knowledge of PostgreSQL Understanding of basic database operations (save, update, delete) in TypeORM Project Setup In this article, we will create a simple NestJS application to demonstrate the importance of transactional queries when multiple database write or update operations

2026-06-10 原文 →