Sử dụng cơ sở dữ liệu để theo dõi khóa / mở khóa của các đối tượng


8

Tôi có một yêu cầu để theo dõi hành động khóa / mở khóa của các đối tượng. Trước khi bất kỳ hành động nào được thực hiện trên một đối tượng (hợp đồng, đối tác, v.v.), một locksự kiện được đưa ra. Sau khi hành động kết thúc, nó đưa ra unlocksự kiện.

Tôi muốn có được những đối tượng đã bị khóa nhưng chưa được mở khóa. Mục tiêu là làm cho truy vấn nhanh chóng và tránh bế tắc.

Dưới đây là bảng

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

Tôi sử dụng truy vấn bên dưới để phản đối các đối tượng chưa được mở khóa:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

Nó hoạt động chính xác và kết quả a, bd.

Câu hỏi của tôi là: - Giải pháp của tôi có đủ để tránh bế tắc không? Có bất kỳ vấn đề có thể xảy ra nếu có rất nhiều INSERTtrong quá trình thực hiện truy vấn? - Bạn có cách nào khác (tốt hơn) để giải quyết điều này không?

CẬP NHẬT

Tôi xin lỗi vì đã không đặt bối cảnh vào câu hỏi. Thiết kế cơ sở dữ liệu ở trên không phải để thay thế khóa cơ sở dữ liệu.

Chúng tôi có một hệ thống bên ngoài mà chúng tôi gọi nó từ hệ thống của chúng tôi. Nó yêu cầu gọi lockunlockphương thức trên hệ thống của họ trước mỗi hành động thực hiện trên một đối tượng (có thể là hợp đồng hoặc đối tác).

Gần đây, chúng tôi có tình huống máy chủ gặp sự cố và chúng tôi phải khởi động lại nó. Thật không may, các quy trình đang chạy lockđã không có cơ hội gọi unlockđể giải phóng các đối tượng, do đó dẫn đến một số vấn đề khác khi hệ thống của chúng tôi kết nối lại với bên ngoài.

Vì vậy, chúng tôi muốn cung cấp một khả năng để theo dõi từng lockcuộc gọi. Khi khởi động lại máy chủ, chúng tôi sẽ gọi unlockcác đối tượng đã bị khóa trước đó.

Cảm ơn Remus Rusanu đã chỉ ra rằng câu hỏi của tôi đang sử dụng nguyên mẫu DDL. Đây là lần đầu tiên tôi đăng câu hỏi lên DBA và tôi xin lỗi vì đã không đọc FAQ.

Cảm ơn

Câu trả lời:


11

Mục tiêu là làm cho truy vấn nhanh chóng và tránh bế tắc.

Đây là một mục tiêu không thực tế. Bế tắc được xác định bởi ứng dụng có được các khóa và nằm ngoài tầm kiểm soát về cách bạn thực hiện khóa. Điều tốt nhất bạn có thể hy vọng là phát hiện ra bế tắc.

Thực hiện các khóa như hồ sơ là vấn đề. Hàng vẫn tồn tại và bạn sẽ rò rỉ khóa khi sự cố ứng dụng, ngay cả khi việc triển khai hoàn hảo . Làm ổ khóa với tiếng vỗ tay . Họ có ngữ nghĩa giao dịch rõ ràng và các bế tắc được phát hiện bởi động cơ.

Tuy nhiên, nhìn vào những gì bạn đang cố gắng để đạt được, ít có khả năng bạn cần ổ khóa ở tất cả . Bạn đang mô tả một hàng đợi (chọn mục tiếp theo có sẵn để xử lý và tránh xung đột => hàng đợi, không khóa). Đọc Sử dụng bảng dưới dạng Hàng đợi .

Đối với việc thực hiện cụ thể của bạn (sử dụng bảng giữ lịch sử khóa) Tôi phải trung thực: đó là một thảm họa. Để bắt đầu với thiết kế bảng hoàn toàn không phù hợp với mục đích sử dụng: bạn truy vấn theo tên nhưng bảng là một đống không có chỉ mục. Nó có một cột danh tính không có lý do rõ ràng. Bạn có thể trả lời rằng đó chỉ là bảng 'mã giả', nhưng đây là DBA.SE, bạn không đăng ở đây DDL không đầy đủ!

Nhưng quan trọng hơn, việc thực hiện không thực hiện khóa! Không có gì để ngăn hai người dùng 'khóa' cùng một đối tượng hai lần. 'Khóa' của bạn phụ thuộc hoàn toàn vào người gọi hành xử kỳ diệu một cách chính xác. Ngay cả một ứng dụng bằng văn bản tốt nhất cũng không thể sử dụng khóa này, bởi vì không có cách nào để kiểm tra và có được khóa một cách nguyên tử . Hai người dùng có thể kiểm tra, kết luận rằng 'a' đã được mở khóa và đồng thời chèn ('a', 1)bản ghi. Ít nhất bạn sẽ cần một ràng buộc duy nhất. Tất nhiên, điều này sẽ phá vỡ ngữ nghĩa của "đếm các khóa so với mở khóa để xác định trạng thái".

Xin lỗi để nói, nhưng đây là một thực hiện lớp F.

CẬP NHẬT

Vì vậy, chúng tôi muốn cung cấp khả năng theo dõi từng cuộc gọi khóa. Khi khởi động lại máy chủ, chúng tôi sẽ gọi mở khóa trên các đối tượng đã bị khóa trước đó.

Không tham gia vào giao dịch phân phối cam kết hai pha với hệ thống từ xa, tất cả những gì bạn có thể làm là 'nỗ lực tốt nhất', bởi vì có nhiều điều kiện cuộc đua giữa bạn viết 'mở khóa' và thực sự gọi 'mở khóa' trên hệ thống bên thứ ba . Như một nỗ lực tốt nhất, đây là khuyến nghị của tôi:

  • tạo một bảng đơn giản để theo dõi các khóa:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • trước khi gọi hãy lockchèn khóa vào bảng của bạn và cam kết .

  • Sau khi gọi, unlock xóa khóa khỏi bảng của bạn và cam kết
  • khi khởi động hệ thống nhìn vào bảng. bất kỳ hàng nào có khóa trái từ lần chạy trước và phải được 'mở khóa'. Gọi unlockcho mỗi hàng, sau đó xóa hàng. Chỉ sau khi tất cả các khóa đang chờ xử lý đã được 'mở khóa', bạn mới có thể tiếp tục ứng dụng hoạt động bình thường.

Tôi đang đề xuất điều này bởi vì cái bàn sẽ nhỏ lại. Bất cứ lúc nào nó chỉ chứa khóa hiện tại, hoạt động. Nó sẽ không phát triển quảng cáo và gây ra vấn đề sau đó do kích thước tuyệt đối. Là tầm thường để xem những gì bị khóa.

Tất nhiên việc thực hiện này không cung cấp một lịch sử kiểm toán về những gì đã bị khóa bởi ai và khi nào. Bạn có thể thêm rằng nếu cần, dưới dạng một bảng khác, trong đó bạn chỉ chèn các sự kiện (khóa hoặc mở khóa) và không bao giờ truy vấn để tìm ra các khóa 'mồ côi'.

Bạn vẫn phải chuẩn bị các cuộc gọi để 'mở khóa' để thất bại trong khi khởi động vì bạn không thể đảm bảo rằng hệ thống của bạn không gặp sự cố sau khi gọi unlocknhưng trước khi xóa hàng (nói cách khác, bảng của bạn và hệ thống bên thứ ba đã bị tách rời và có sự khác biệt phiên bản của sự thật). Một lần nữa, bạn không thể ngăn chặn giao dịch phân tán này và tôi sẽ không bao giờ tư vấn cho DTC.


4

Điều này sẽ giết chết đồng thời trên ứng dụng của bạn. SQL Server đã có mọi thứ cần thiết để tránh cập nhật cùng một hàng cùng một lúc, vì vậy thực sự không cần phải làm điều đó.

Bế tắc có thể xảy ra vì một số lý do, vì vậy tôi khuyên bạn nên điều tra những lý do đó trước khi xây dựng hệ thống khóa của riêng bạn. Bạn có thể chụp các biểu đồ khóa chết từ phiên XE sức khỏe hệ thống và bắt đầu phân tích chúng.

Thông thường các khóa chết là kết quả của việc lấy khóa trên các đối tượng theo các thứ tự khác nhau, vì vậy bạn nên cố gắng lấy khóa luôn theo cùng một thứ tự. Có nhiều lý do khác khiến bế tắc có thể xảy ra, nhưng hướng dẫn chung là các giao dịch ngắn giữ khóa trong một khoảng thời gian ngắn, vì vậy điều chỉnh truy vấn của bạn bằng mã tốt hơn, chỉ mục và chiến lược phân chia có thể sẽ là cách tốt nhất để thoát khỏi bế tắc.

Loại bế tắc "Độc giả chặn nhà văn" có thể được giảm thiểu bằng cách chuyển cơ sở dữ liệu sang Đọc cách ly ảnh chụp đã cam kết . Nếu ứng dụng của bạn được xây dựng với tâm lý bi quan, bạn nên xem xét và kiểm tra mọi thứ thật cẩn thận trước khi kích hoạt tùy chọn này trên cơ sở dữ liệu của bạn.

Trong trường hợp bạn khăng khăng đi xuống tuyến đường "hệ thống khóa tùy chỉnh", tại leat hãy sử dụng một cái gì đó đảm bảo việc mở khóa trong trường hợp có lỗi với ứng dụng. Bạn có thể muốn điều tra thủ tục lưu trữ sp_getapplock tích hợp sẵn, bạn sẽ làm một cái gì đó tương tự như những gì bạn dường như sau đó.

CẬP NHẬT: Sau khi đọc câu hỏi đã chỉnh sửa của bạn, đây là một cách khác để diễn đạt cùng một truy vấn:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 1;

Nó sẽ hoạt động nếu các đối tượng được phép khóa chỉ một lần, dường như đó là toàn bộ điểm của hệ thống khóa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.