Đặc điểm của các cơ chế bên trong của PostgreSQL cho phép nó hoạt động rất nhanh trong một số trường hợp và “không nhanh lắm” trong một số trường hợp khác. Hôm nay chúng ta sẽ tập trung vào một ví dụ kinh điển về xung đột giữa cách thức hoạt động của DBMS và những gì nhà phát triển thực hiện với nó - Nguyên tắc CẬP NHẬT so với MVCC.
Câu chuyện ngắn gọn từ :
Khi một hàng được sửa đổi bằng lệnh UPDATE, hai thao tác thực sự được thực hiện: DELETE và INSERT. TRONG phiên bản hiện tại của chuỗi xmax được đặt bằng số lượng giao dịch đã thực hiện CẬP NHẬT. Sau đó nó được tạo ra phiên bản mới cùng một dòng; giá trị xmin của nó trùng với giá trị xmax của phiên bản trước.
Một thời gian sau khi giao dịch này hoàn tất, phiên bản cũ hay mới tùy thuộc vào COMMIT/ROOLBACK, sẽ được công nhận "chết" (bộ dữ liệu chết) khi đi qua VACUUM theo bảng và xóa.

Nhưng điều này sẽ không xảy ra ngay lập tức, nhưng các vấn đề với “người chết” có thể xảy ra rất nhanh - nếu lặp đi lặp lại hoặc trên một chiếc bàn lớn, một lát sau bạn sẽ gặp phải tình huống tương tự .
#1: Tôi thích di chuyển nó
Giả sử phương thức của bạn đang hoạt động theo logic nghiệp vụ và đột nhiên nó nhận ra rằng cần phải cập nhật trường X trong một số bản ghi:
UPDATE tbl SET X = <newX> WHERE pk = $1;Sau đó, khi quá trình thực thi diễn ra, trường Y cũng cần được cập nhật:
UPDATE tbl SET Y = <newY> WHERE pk = $1;... và sau đó là Z - tại sao lại lãng phí thời gian vào những chuyện vặt vãnh?
UPDATE tbl SET Z = <newZ> WHERE pk = $1;Hiện tại chúng ta có bao nhiêu phiên bản của bản ghi này trong cơ sở dữ liệu? Đúng, 4 miếng! Trong số này, một cái có liên quan và 3 cái sẽ phải được dọn dẹp sau bạn bằng [auto]VACUUM.
Đừng làm theo cách này! Sử dụng cập nhật tất cả các trường trong một yêu cầu — hầu như logic của phương thức luôn có thể được thay đổi như thế này:
UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;#2: Sử dụng LÀ KHÁC BIỆT, Luke!
Vì vậy, bạn vẫn muốn (ví dụ như trong quá trình sử dụng tập lệnh hoặc trình chuyển đổi). Và một cái gì đó như thế này bay vào kịch bản:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;Một yêu cầu ở dạng này xảy ra khá thường xuyên và hầu như không phải để điền vào một trường mới trống mà để sửa một số lỗi trong dữ liệu. Đồng thời, bản thân cô tính chính xác của dữ liệu hiện có hoàn toàn không được tính đến - nhưng vô ích! Nghĩa là, hồ sơ được viết lại, ngay cả khi nó chứa chính xác những gì cần thiết - nhưng tại sao? Hãy sửa nó:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>; Nhiều người không biết đến sự tồn tại của một nhà điều hành tuyệt vời như vậy, vì vậy đây là một mẹo nhỏ về IS DISTINCT FROM và các toán tử logic khác để trợ giúp:

... và một chút về các thao tác trên phức tạp ROW()-biểu thức:

#3: Tôi nhận ra người yêu của mình bằng cách... chặn
đang được khởi chạy hai quá trình song song giống hệt nhau, mỗi mục cố gắng đánh dấu mục đó là “đang tiến hành”:
UPDATE tbl SET processing = TRUE WHERE pk = $1;Ngay cả khi các quy trình này thực sự thực hiện những việc độc lập với nhau nhưng trong cùng một ID, khách hàng thứ hai sẽ bị “khóa” yêu cầu này cho đến khi giao dịch đầu tiên hoàn tất.
Giải pháp # 1: nhiệm vụ được giảm xuống nhiệm vụ trước đó
Chúng ta hãy thêm nó một lần nữa IS DISTINCT FROM:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;Trong biểu mẫu này, yêu cầu thứ hai sẽ không thay đổi bất cứ điều gì trong cơ sở dữ liệu, mọi thứ vẫn như cũ - do đó, việc chặn sẽ không xảy ra. Tiếp theo, chúng tôi xử lý thực tế “không tìm thấy” bản ghi trong thuật toán được áp dụng.
Giải pháp # 2: khóa tư vấn
Một chủ đề lớn cho một bài viết riêng biệt mà bạn có thể đọc về .
Giải pháp # 3: cuộc gọi ngu ngốc
Nhưng đây chính xác là điều sẽ xảy ra với bạn làm việc đồng thời với cùng một bản ghi? Hay bạn đã nhầm lẫn với các thuật toán gọi logic nghiệp vụ ở phía máy khách chẳng hạn? Và nếu bạn nghĩ về nó?..
Nguồn: www.habr.com
