Trong một số bài viết trước đây của tôi về điều chỉnh hiệu suất, tôi đã thảo luận về nhiều kiểu chờ đợi và cách chúng biểu thị các nút thắt tài nguyên khác nhau. Tôi đang bắt đầu một loạt bài mới về các tình huống trong đó cơ chế đồng bộ hóa được gọi là chốt là một nút cổ chai về hiệu suất và cụ thể là các chốt không phải trang. Trong bài đăng đầu tiên này, tôi sẽ giải thích lý do tại sao cần phải có chốt, chúng thực sự là gì và chúng có thể là một nút thắt cổ chai như thế nào.
Tại sao cần có chốt?
Nguyên lý cơ bản của khoa học máy tính là bất cứ khi nào cấu trúc dữ liệu tồn tại trong hệ thống đa luồng, cấu trúc dữ liệu phải được bảo vệ theo một cách nào đó. Sự bảo vệ này cung cấp các điều khoản sau:
- (Đảm bảo) Một luồng không thể thay đổi cấu trúc dữ liệu trong khi một luồng khác đang đọc nó
- (Đảm bảo) Một luồng không thể đọc một cấu trúc dữ liệu trong khi một luồng khác đang thay đổi cấu trúc đó
- (Đảm bảo) Không thể thay đổi cấu trúc dữ liệu bởi hai hoặc nhiều luồng cùng một lúc
- (Tùy chọn) Cho phép hai hoặc nhiều luồng đọc cấu trúc dữ liệu cùng một lúc
- (Tùy chọn) Cho phép các chuỗi xếp hàng theo thứ tự để truy cập vào cấu trúc dữ liệu
Điều này có thể được thực hiện theo một số cách, bao gồm:
- Cơ chế chỉ cho phép một luồng duy nhất tại một thời điểm có quyền truy cập vào cấu trúc dữ liệu. SQL Server thực hiện cơ chế này và gọi nó là một spinlock. Điều này cho phép # 1, # 2 và # 3 ở trên.
- Cơ chế cho phép nhiều luồng đọc cấu trúc dữ liệu cùng một lúc (tức là chúng có quyền truy cập được chia sẻ), cho phép một luồng duy nhất có quyền truy cập độc quyền vào cấu trúc dữ liệu (loại trừ tất cả các luồng khác) và triển khai một cách xếp hàng công bằng để truy cập. SQL Server thực hiện cơ chế này và gọi nó là chốt. Điều này cho phép tất cả năm điều kiện ở trên.
Vậy tại sao SQL Server lại sử dụng cả spinlock và chốt? Một số cấu trúc dữ liệu được truy cập thường xuyên đến nỗi một chốt đơn giản là quá đắt và do đó, một spinlock rất nhẹ được sử dụng để thay thế. Hai ví dụ về cấu trúc dữ liệu như vậy là danh sách các bộ đệm trống trong vùng đệm và danh sách các ổ khóa trong trình quản lý khóa.
Latch là gì?
Chốt là một cơ chế đồng bộ hóa bảo vệ một cấu trúc dữ liệu duy nhất và có ba loại chốt lớn trong SQL Server:
- Chốt bảo vệ một trang tệp dữ liệu khi nó đang được đọc từ đĩa. Chúng hiển thị khi PAGEIOLATCH_XX đang đợi và tôi đã thảo luận về chúng trong bài đăng này.
- Chốt bảo vệ quyền truy cập vào trang tệp dữ liệu đã có trong bộ nhớ (trang 8KB trong vùng đệm thực sự chỉ là một cấu trúc dữ liệu). Chúng hiển thị khi PAGELATCH_XX đang đợi và tôi đã thảo luận về chúng trong bài đăng này.
- Chốt bảo vệ cấu trúc dữ liệu không phải trang. Chúng hiển thị dưới dạng LATCH_SH và LATCH_EX đang đợi.
Trong loạt bài này, chúng ta sẽ tập trung vào loại chốt thứ ba.
Bản thân chốt là một cấu trúc dữ liệu nhỏ và bạn có thể coi nó có ba thành phần:
- Mô tả tài nguyên (về những gì nó đang bảo vệ)
- Trường trạng thái cho biết chế độ nào hiện đang giữ chốt, có bao nhiêu luồng giữ chốt ở chế độ đó và liệu có bất kỳ luồng nào đang chờ (cộng với những thứ khác mà chúng tôi không phải quan tâm)
- Một hàng đợi trước-ra-trước gồm các luồng đang chờ truy cập vào cấu trúc dữ liệu và chế độ truy cập mà chúng đang chờ đợi (được gọi là hàng đợi)
Đối với các chốt không phải trang, chúng tôi sẽ tự giới hạn mình chỉ xem xét các chế độ truy cập SH (chia sẻ) để đọc cấu trúc dữ liệu và EX (độc quyền) để thay đổi cấu trúc dữ liệu. Có những chế độ khác kỳ lạ hơn, nhưng chúng hiếm khi được sử dụng và sẽ không xuất hiện dưới dạng điểm tranh cãi, vì vậy tôi sẽ giả vờ rằng chúng không tồn tại trong phần còn lại của cuộc thảo luận này.
Một số bạn có thể biết rằng còn có những phức tạp sâu hơn xung quanh siêu chốt / chốt con và phân vùng chốt để có khả năng mở rộng, nhưng chúng tôi không cần phải đi sâu vào mục đích của loạt bài này.
Có được một chốt
Khi một chuỗi muốn có được chốt, nó sẽ xem xét trạng thái của chốt.
Nếu luồng muốn lấy chốt ở chế độ EX, nó chỉ có thể làm như vậy nếu không có luồng nào giữ chốt ở bất kỳ chế độ nào. Nếu đúng như vậy, luồng sẽ nhận chốt ở chế độ EX và đặt trạng thái để chỉ ra điều đó. Nếu có một hoặc nhiều luồng đã giữ chốt, luồng sẽ đặt trạng thái để chỉ ra rằng có một luồng đang chờ, tự vào ở cuối hàng đợi và sau đó bị tạm dừng (trên danh sách người phục vụ của bộ lập lịch ) đang đợi LATCH_EX.
Nếu luồng muốn lấy chốt ở chế độ SH, nó chỉ có thể làm như vậy nếu không có luồng nào giữ chốt hoặc luồng duy nhất giữ chốt đang ở chế độ SH mode * và * không có luồng nào đang đợi để lấy chốt. Nếu đúng như vậy, luồng sẽ nhận chốt ở chế độ SH mode, đặt trạng thái để chỉ ra điều đó và tăng số lượng luồng đang giữ chốt. Nếu chốt được giữ ở chế độ EX hoặc có một hoặc nhiều luồng đang chờ, thì luồng sẽ đặt trạng thái để chỉ ra rằng có một luồng đang chờ, tự vào ở cuối hàng đợi, và sau đó bị tạm dừng chờ LATCH_SH.
Việc kiểm tra các luồng đang chờ được thực hiện để đảm bảo tính công bằng cho một luồng đang chờ chốt ở chế độ EX. Nó sẽ chỉ phải đợi các chủ đề giữ chốt ở chế độ SH mode đã có được chốt trước khi nó bắt đầu chờ đợi. Nếu không có kiểm tra đó, một thuật ngữ khoa học máy tính có tên là 'chết đói' có thể xảy ra, khi một luồng liên tục các luồng có được chốt trong chế độ SH mode sẽ ngăn luồng EX-mode không bao giờ có thể lấy được chốt.
Phát hành chốt
Nếu sợi giữ chốt ở chế độ EX, nó sẽ bỏ đặt trạng thái hiển thị chốt được giữ ở chế độ EX và sau đó kiểm tra xem có sợi nào đang chờ không.
Nếu luồng giữ chốt ở chế độ SH, nó sẽ giảm số lượng luồng ở chế độ SH. Nếu số đếm bây giờ là khác 0, chuỗi giải phóng được thực hiện với chốt. Nếu số lượng * bây giờ là * 0, nó sẽ bỏ đặt trạng thái hiển thị chốt được giữ ở chế độ SH và sau đó kiểm tra xem có bất kỳ luồng nào đang chờ hay không.
Nếu không có luồng nào đang đợi, luồng giải phóng được thực hiện bằng chốt.
Nếu phần đầu của hàng đợi đang đợi chế độ EX, thì chuỗi giải phóng sẽ thực hiện như sau:
- Đặt trạng thái để hiển thị chốt được giữ ở chế độ EX
- Xóa chuỗi chờ khỏi đầu hàng đợi và đặt nó làm chủ sở hữu của chốt
- Báo hiệu chuỗi chờ rằng nó là chủ sở hữu và hiện có thể chạy được (theo khái niệm, di chuyển chuỗi chờ từ danh sách người phục vụ trên bộ lập lịch của nó sang hàng đợi có thể chạy trên bộ lập lịch)
- Và nó đã xong với chốt
Nếu phần đầu của hàng đợi đang đợi ở chế độ SH (chỉ có thể là trường hợp nếu chuỗi giải phóng ở chế độ EX), thì chuỗi giải phóng sẽ thực hiện như sau:
- Đặt trạng thái để hiển thị chốt được giữ ở chế độ SH mode
- Đối với tất cả các chuỗi trong hàng đợi đang chờ SH mode
- Xóa chuỗi chờ khỏi đầu hàng đợi
- Tăng số lượng các sợi chỉ giữ chốt
- Báo hiệu chuỗi chờ rằng nó là chủ sở hữu và hiện có thể chạy được
- Và nó đã xong với chốt
Làm thế nào để các chốt có thể trở thành điểm gây chú ý?
Không giống như ổ khóa, các chốt thường chỉ được giữ trong thời gian thực hiện thao tác đọc hoặc thay đổi, vì vậy chúng khá nhẹ, nhưng do sự không tương thích giữa SH và EX, chúng có thể lớn bằng một điểm tranh chấp như ổ khóa. Điều này có thể xảy ra khi nhiều luồng đang cố gắng lấy chốt ở chế độ EX (mỗi lần chỉ có thể) hoặc khi nhiều luồng đang cố gắng lấy chốt ở chế độ SH và một luồng khác giữ chốt ở chế độ EX.
Tóm tắt
Càng nhiều luồng trong hệ thống tranh giành chốt ‘nóng’, sự tranh chấp càng cao và ảnh hưởng tiêu cực đến thông lượng công việc càng nhiều. Bạn có thể đã nghe nói về các vấn đề tranh chấp chốt nổi tiếng, chẳng hạn như xung quanh bitmap phân bổ tempdb, nhưng tranh chấp cũng có thể xảy ra đối với các chốt không phải trang.
Bây giờ, tôi đã cung cấp cho bạn đủ kiến thức cơ bản để hiểu về chốt và cách chúng hoạt động, trong một vài bài viết tiếp theo, tôi sẽ xem xét một số vấn đề tranh chấp chốt ngoài trang ngoài đời thực và giải thích cách ngăn chặn hoặc giải quyết chúng.