Tìm hiểu về các loại Khoá (Explicit Locking) trong PostgreSQL

Tìm hiểu về các loại Khoá (Explicit Locking) trong PostgreSQL

Tin ngắn hàng ngày dành cho bạn
  • Bắt đầu kỳ nghỉ tết rồi nên mình cũng không đăng bài nữa. Hẹn gặp lại các bạn qua tết nha 😁

    » Xem thêm
  • Tiếp tục về jj. Đang thắc mắc là nó mới thế liệu có ai làm mấy phần mềm dạng GUI cho dễ nhìn chưa. Kiểu giống như git thì có quá nhiều rồi không đếm xuể.

    May quá, tác giả tổng hợp lại luôn rồi Community-built tools around Jujutsu 🥳

    » Xem thêm
  • Turso thông báo rằng họ đang viết lại SQLite bằng Rust. Thế là lại có thêm một bằng chứng nữa cũng cố cho câu nói Rust đang "tái định nghĩa" lại nhiều thứ.

    Nhưng nguyên nhân sâu xa mới thú vị. Tại sao họ lại làm vậy? Ai cũng biết SQLite là nguồn mở, ai cũng có thể tạo bản sao (fork) để chỉnh sửa lại theo ý mình. Lẽ nào nhóm của Turso không thích hoặc không tin vào C - vốn là ngôn ngữ dùng để cấu thành SQLite.

    Mình xin kể chuyện một chút. Turso là một bên cung cấp dịch vụ máy chủ cơ sở dữ liệu dựa trên SQLite, họ đã thực hiện một vài tùy chỉnh trên bản sao của SQLite để phục vụ cho mục đích của mình, gọi nó là libSQL. Họ "hào phóng" cho cộng đồng đóng góp thoải mái.

    Quay trở lại SQLite là mã nguồn mở chứ không phải là đóng góp mở. Chỉ có một nhóm người đứng đằng sau duy trì mã nguồn này, và họ không tiếp nhận yêu cầu kéo (pull request) từ những người khác. Đồng nghĩa mọi thay đổi hoặc tính năng đều là của nhóm người này tạo ra. Có vẻ như SQLite rất phổ biến nhưng cộng đồng không thể làm điều mà họ muốn là đóng góp cho sự phát triển của nó.

    Chúng ta biết rằng hầu hết ứng dụng mã nguồn mở thường đi kèm với một thư mục "tests" với các bài kiểm tra rất nghiêm ngặt. Điều đó giúp cho sự cộng tác trong phát triển trở nên dễ dàng hơn. Nếu muốn chỉnh sửa hoặc thêm một tính năng mới, trước hết bạn cần phải đảm bảo sự thay đổi vượt qua được tất cả bài kiểm tra. Nhiều thông tin cho rằng SQLite không công khai bộ kiểm tra này. Điều này vô tình gây khó khăn cho những ai muốn chỉnh sửa mã nguồn. Vì họ không chắc chắn rằng liệu triển khai mới của mình có phù hợp với những tính năng cũ hay không.

    tursodatabase/limbo là dự án viết lại SQLite bằng Rust đã nhắc đến ở đầu bài. Họ nói rằng nó hoàn toàn tương thích với SQLite và nguồn mở hoàn toàn. limbo đang trong giai đoạn hoàn thiện. Chúng ta hãy chờ xem kết quả trong tương lai thế nào nhé. Bài viết chi tiết tại Introducing Limbo: A complete rewrite of SQLite in Rust.

    » Xem thêm

Vấn đề

Làm việc với SQL hay PostgreSQL đủ lâu nhưng có bao giờ bạn đặt câu hỏi sẽ thế nào nếu như thực hiện một lệnh SELECT và DROP trên cùng một table cùng lúc? Lệnh nào sẽ chạy trước lệnh nào sẽ chạy sau, độ ưu tiên của các câu lệnh như thế nào hay là PostgreSQL dựa vào đâu để phân mức ưu tiên? Bài viết này hôm nay tôi xin phép trình bày cách mà PostgreSQL sử dụng Khoá (Locks) để giải quyết xung đột dữ liệu như thế nào nhé.

Tại sao lại cần khoá?

PostgreSQL cung cấp nhiều chế độ khóa khác nhau để kiểm soát quyền truy cập đồng thời vào dữ liệu trong bảng. Các khoá này có thể được sử dụng để can thiệp vào các tình huống của MVCC (Multi Version Concurrency Control).

Ngoài ra, hầu hết các lệnh PostgreSQL có được các khóa của các chế độ thích hợp để đảm bảo rằng các bảng được tham chiếu không bị xóa hoặc sửa đổi theo cách không mong muốn trong khi các lệnh đang được thực thi. Ví dụ như lệnh TRUNCATE không thể thực hiện đồng thời với các lệnh thao tác khác trên cùng một bảng như SELECT..., để làm được điều TRUNCATE được cấp phát cho một khoá gọi là ACCESS EXCLUSIVE.

Các cấp khoá

Postgres có các cấp khoá sau đây:

  • Table-Level Locks
  • Row-Level Locks
  • Page-Level Locks
  • Advisory Locks

Các cấp khoá mô tả miền xung đột của các lệnh PostgreSQL ở các cấp độ bảng (table), dòng (row) hay là trang (page)...

Tuy nhiên trong bài viết này tôi xin phép trình bày hai cấp khoá là Table và Row. Bạn đọc có thể tìm hiểu thêm các loại khoá tại trang tài liệu của PostgreSQL.

Khoá cấp bảng (Table-Level Locks)

Là các khoá ở quy mô bảng, các khoá này được cấp phát tự động hoặc thông qua lệnh LOCK.

Tự động có nghĩa là phụ thuộc vào lệnh SQL nào khi gọi thì bạn ngay lập tức sẽ được cấp pháp khoá đó. Dưới đây là một số câu lệnh quen thuộc và các khoá được cấp tự động tương ứng khi bạn gọi chúng.

  • SELECT: ACCESS SHARE
  • UPDATE, DELETE, INSERT: ROW EXCLUSIVE
  • CREATE INDEX: SHARE
  • CREATE TRIGGER: SHARE ROW EXCLUSIVE
  • DROP TABLE, TRUNCATE: ACCESS EXCLUSIVE

Nhiều khoá và chi tiết hơn xem lại trang tài liệu của postgres.

Dưới đây là bảng thể hiện sự xung đột của các khoá với nhau.

table locking

Tôi có thể giải thích như sau: nhìn vào bảng chúng ta thấy khoá ACCESS SHARE sẽ xung đột với ACCESS EXCLUSIVE vì thế trong các transaction nếu lệnh nào đang được thực hiện trước (nắm khoá ACCESS SHARE) thì lệnh sau (nắm khoá ACCESS EXCLUSIVE) phải chờ cho đến khi transaction được hoàn thành mới có thể tiếp tục và ngược lại.

-- (1)
BEGIN; 
-- (2)
SELECT * FROM users; -- ACCESS SHARE LOCKS
-- (4)
COMMIT;
-- (3)
TRUNCATE users; -- ACCESS EXCLUSIVE LOCKS

Hãy thử chạy các lệnh trên trên hai tiến trình khác nhau theo các bước (1) (2) (3) (4) để xem điều gì xảy ra nhé.

Khoá cấp hàng (Row-Level Locks)

Các khoá cấp hàng được cấp phát bằng cách xác định tên của khoá trong câu truy vấn. Có 4 khoá là:

  • FOR UPDATE
  • FOR NO KEY UPDATE
  • FOR SHARE
  • FOR KEY SHARE

row locking

FOR UPDATE khiến các hàng được truy xuất bởi SELECT bị khóa như thể để cập nhật. Điều này giúp chúng không bị khóa, sửa đổi hoặc xóa bởi các transaction khác cho đến khi transaction hiện tại kết thúc. Nghĩa là, các transaction khác như DELETE hoặc UPDATE một trong số các hàng này sẽ bị chặn cho đến khi transaction hiện tại kết thúc.

Tương tự như Table-Level Locks, bảng trên mô tả xung đột giữa các khoá. Các khoá xung đột với nhau sẽ phải đợi cho đến khi khoá cấp phát trước được thực hiện xong thì mới đến lượt mình.

Ví dụ bạn phát triển một tính năng đổi quà, có một bảng quà chứa số lượng còn lại của quà muốn đổi và bảng lưu số điểm hợp lệ của bạn dùng để đổi quà. Khi đổi quà bạn cần lấy ra số lượng quà còn lại và số điểm của bạn, kiểm tra qua nhiều điều kiện khắc nghiệt và tốn rất nhiều thời gian nữa mới có thể đổi. Giả sử trong lúc yêu cầu thứ nhất đã lấy ra số lượng quà còn lại là 1 và có yêu cầu thứ hai ngay lập tức cũng lấy ra số lượng quà còn lại là 1 thì điều gì sẽ xảy ra nếu như cả hai tiếp tục đổi quà? Để ngăn chặn điều đó bạn có thể sử dụng Row-Level Locks, tức là khi một transaction đang lấy số lượng quà còn lại để bắt đầu validate thì transaction thứ hai cần chờ đợi cho đến khi transaction đầu kết thúc.

Deadlock

Deadlock xảy ra khi hai hoặc nhiều transaction giữ các khoá mà bên kia mong muốn. Ví dụ transaction thứ nhất có được khoá ACCESS EXCLUSIVE trên bảng A và sau đó muốn có tiếp khoá đó trên bảng B trong khi transaction thứ hai đã có khoá ACCESS EXCLUSIVE trên bảng B và bây giờ lại muốn có khoá đó trên bảng A thì lúc đó không có transaction nào có thể tiếp tục. PostgreSQL tự động phát hiện tình huống này và giải quyết bằng cách huỷ bỏ một trong các transaction có liên quan, transaction bị huỷ bỏ không thể đoán trước.

Deadlock cũng có thể xảy ra ở trong các khoá cấp hàng khi các transaction cũng giữ khoá của nhau.

Biện pháp bảo vệ tốt nhất chống lại các deadlock nói chung là tránh để cho các lệnh có được các khóa trên nhiều đối tượng (table/row) theo một thứ tự xác định.

Nếu transaction được thực hiện trên nhiều đối tượng liên quan, cách tốt nhất là làm sao cho thời gian thực hiện transaction càng ngắn càng tốt. Tránh giữ các transaction trong thời gian dài. Ví dụ cho một thực hành tồi là transaction sẽ được thực thi tiếp cho đến khi chờ đợi thông tin nhập của người dùng xong.

Tổng kết

Bài viết trên đây tôi đã trình bày về cách mà PostgreSQL sử dụng khoá để quản lý quyền truy cập đồng thời vào dữ liệu trong bảng. Mặc dù là dành riêng cho PostgreSQL nhưng tư tưởng sử dụng khoá vẫn tồn lại trong các loại cơ sở dữ liệu SQL khác như MySQL, SQL Server... Nắm được cách hoạt động của khoá sẽ giúp bạn ứng biến với những tình huống trong việc phát triển ứng dụng cùng SQL.

Cao cấp
Hello

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (1)

Nội dung bình luận...
Avatar
Trần Ngọc Hải2 năm trước
Bài viết quá hay quá chi tiết cảm ơn tác giả
Trả lời
Bấm hoặc cuộn mạnh để sang bài mới