8

Tôi có một thủ tục được lưu trữ trong mysql thats để thực hiện một tác vụ cần được đồng bộ hóa sao cho nếu hai ứng dụng gọi thủ tục được lưu trữ, chỉ một người có thể truy cập một phần mã để thực hiện tác vụ , giữ cho người khác bị chặn cho đến khi người đầu tiên hoàn thành nhiệm vụ.Thực hiện thủ tục được lưu trữ đồng bộ trong mysql

DELIMITER $$ 
CREATE PROCEDURE SP_GEN_ID(IN NAME VARCHAR(20)) 

BEGIN 
    DECLARE maxLen int default 0; 
START TRANSACTION; 
    #the section of code that needs to be synchronized 
COMMIT 
END; 
$$ 

DELIMITER ; 

Vì vậy, nếu hai ứng dụng gọi thủ tục lưu trữ cùng một lúc, tác vụ phải được đồng bộ hóa.

a. Nhưng Bắt đầu TRANSACTIONCOMMIT KHÔNG đồng bộ hóa quá trình thực hiện.

b. Và KHÓA BẢNG tableA không thể được sử dụng trong quy trình lưu trữ để đảm bảo quá trình đồng bộ hóa.

c. Tôi đã cố gắng đồng bộ hóa cuộc gọi thủ tục được lưu trữ ở cấp ứng dụng. Tôi đã sử dụng

boost_interprocess scoped_lock lock();

Nó hoạt động hoàn toàn tốt đẹp trong tăng 1.41

Nhưng interprocess khóa mutex không được hỗ trợ trong thư viện tăng 1,34, đó là những gì có sẵn trong trường hợp của tôi.

Có cách nào để đồng bộ hóa phần thủ tục được lưu trữ của mã sao cho khi hai cuộc gọi được thực hiện đồng thời, một cuộc gọi bị chặn trước khi cuộc gọi khác được thực hiện?

(thêm nội dung sau) mã đã chỉnh sửa: để đưa ra ý tưởng những gì tôi đang cố gắng thực hiện trong khối được đồng bộ hóa của quy trình được lưu trữ.

Nó nhận được id được gán cuối cùng và tăng dần một id và kiểm tra xem nó không được sử dụng cho bản ghi 'tên' khác. Khi tìm thấy id hợp lệ, hãy cập nhật bảng bản ghi id được gán cuối cùng và sau đó liên kết với tên 'được cung cấp'.

DELIMITER $$ 
CREATE PROCEDURE SP_GEN_ID(IN NAME VARCHAR(20)) 

BEGIN 
    DECLARE maxLen int default 0; 
START TRANSACTION; 
    #the section of code that needs to be synchronized 
    SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID';  
findid_loop: 
    LOOP 
    set lastid = lastid + 1; 
    #this is to check whether it was assigned with someother name before. 
    IF not EXISTS (SELECT 1 FROM user_name_id WHERE name_id = lastgenerated) then 
        set nameid = lastgenerated; 
        set found = true; 
        LEAVE findid_loop; 
      END IF; 

      #for loop limit check 
      IF (counter < loopLimit) then 
        set counter = counter + 1; 
        ITERATE findid_loop; 
      ELSE 
        #reached the limit and not found. 
        LEAVE findid_loop; 
      END IF; 
    END LOOP findid_loop; 

    #if a valid id, update last id and assign to name. 
    IF (found) THEN 
      #update the id. 
      update DB_last_id set lastid = nameid where key = 'NAME_ID'; 
      insert into user_name_id values (nameid ,name); 
    ELSE 
      #return an empty string for the application to take action on the failure. 
      set nameid = ''; 
    END IF; 
#end transaction here. 
COMMIT 

END; 
$$ 

DELIMITER ; 
+0

Bảng của bạn sử dụng công cụ lưu trữ nào?Họ phải là InnoDB cho các giao dịch/khóa để làm việc. – eggyal

+0

Công cụ lưu trữ là InnoDB cho các bảng. Nhưng nó vẫn có vẻ không hoạt động. Để xác minh điều này, tôi đã ngủ (15), chờ 15 giây ngay sau khi BẮT ĐẦU GIAO DỊCH. Khi tôi thực hiện các cuộc gọi đồng thời đến thủ tục được lưu trữ, cả hai dường như xuất hiện sau 15 giây. Nếu BẮT ĐẦU GIAO DỊCH đảm bảo đồng bộ hóa, thì cuộc gọi thứ hai đến thủ tục lưu trữ sẽ kết thúc sau 30 giây hoặc lâu hơn. đúng? (15 giây cho cuộc gọi đầu tiên để vượt qua, sau đó cuộc gọi thứ hai đi vào trong GIAO DỊCH BẮT ĐẦU và ngủ trong 15 giây) –

+0

Mã chính xác bạn đang cố gắng đồng bộ hóa là gì? Nếu bạn chỉ đơn giản muốn các hoạt động SQL là nguyên tử, thực hiện chúng trong một giao dịch sẽ thực hiện điều đó. Nếu bạn đang ảnh hưởng đến trạng thái khác, như biến hệ thống, bạn sẽ cần phải có được một khóa trên một bảng/hồ sơ dành riêng cho mục đích này. Và việc thiếu một sự chậm trễ trong cuộc gọi thứ hai của bạn không nhất thiết là dấu hiệu của lỗi khóa (có thể, ví dụ như các vấn đề về bộ nhớ cache làm cho cuộc gọi thứ hai chạy nhanh hơn lần đầu tiên hoặc cuộc gọi thứ hai có thể thực hiện một lệnh khác đường dẫn đến đầu tiên). Vui lòng cung cấp mã đầy đủ. – eggyal

Trả lời

8

Bắt đầu giao dịch với START TRANSACTION không thực sự bắt đầu giao dịch. Truy cập bảng đầu tiên sau START TRANSACTION. Việc mở giao dịch cũng không phải là phương tiện để kiểm soát đồng thời. Nếu bạn cần điều đó, bạn có thể dựa vào hệ thống khóa cố vấn mà MySQL cung cấp thông qua GET_LOCK(), RELEASE_LOCK() và một vài chức năng liên quan khác.

Một cách khác để thực hiện kiểm soát đồng thời, thông qua giao dịch lần này, sẽ là bằng cách dựa vào khóa hàng độc quyền. Vì các câu lệnh SELECT không bị khóa trong InnoDB, việc phát hành truy vấn như vậy bắt đầu một giao dịch, tuy nhiên nó không đặt bất kỳ khóa nào cũng như không tôn trọng bất kỳ khóa nào tồn tại trước đó. Để có câu lệnh SELECT thực sự chặn nếu có giao dịch trước đó hoạt động trên cùng một thông tin (hàng), bạn phải sử dụng mệnh đề FOR UPDATE. Ví dụ:

START TRANSACTION; 
SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID' FOR UPDATE; 
... 

Với xây dựng này sẽ không bao giờ có hai giao dịch đồng thời hoạt động trên cùng một 'NAME_ID' qua báo cáo kết quả SELECT đó đã nói một cách rõ ràng để thực hiện một đọc khóa.

+0

cũng hoạt động tốt! Cảm ơn. –

9

Như đã đề cập trong ý kiến ​​của tôi ở trên, bạn nên thấy rằng một giao dịch là đủ cho hầu hết các nhu cầu; Tuy nhiên, nếu bạn cần phải chờ đợi một cách rõ ràng cho đến khi cuộc gọi khác đã hoàn thành, sử dụng GET_LOCK(str,timeout):

cố gắng để có được một khóa với một tên được đưa ra bởi chuỗi str, sử dụng một thời gian chờ của timeout giây. Trả về 1 nếu khóa đã được lấy thành công, 0 nếu cố gắng hết thời gian (ví dụ, vì một khách hàng khác đã khóa tên trước đây), hoặc NULL nếu xảy ra lỗi (chẳng hạn như hết bộ nhớ hoặc chuỗi bị giết với mysqladmin kill) . Nếu bạn có một khóa thu được với GET_LOCK(), nó được phát hành khi bạn thực hiện RELEASE_LOCK(), thực hiện một mới GET_LOCK(), hoặc kết nối của bạn chấm dứt (bình thường hoặc bất thường). Khóa thu được với GET_LOCK() không tương tác với các giao dịch.Đó là, cam kết một giao dịch không phát hành bất kỳ khóa như vậy thu được trong giao dịch.

Chức năng này có thể được sử dụng để triển khai khóa ứng dụng hoặc để mô phỏng các khóa bản ghi. Tên bị khóa trên cơ sở máy chủ. Nếu một tên đã bị khóa bởi một khách hàng, GET_LOCK() sẽ chặn mọi yêu cầu của một khách hàng khác cho một khóa có cùng tên. Điều này cho phép khách hàng đồng ý về một tên khóa nhất định để sử dụng tên để thực hiện khóa tư vấn hợp tác. Nhưng lưu ý rằng nó cũng cho phép một khách hàng không nằm trong số các khách hàng hợp tác để khóa tên, hoặc vô ý hoặc cố ý, và do đó ngăn chặn bất kỳ khách hàng hợp tác nào từ khóa tên đó. Một cách để giảm khả năng của việc này là sử dụng các tên khóa cụ thể cho từng cơ sở dữ liệu hoặc ứng dụng cụ thể. Ví dụ, sử dụng tên khóa của mẫu db_name.str hoặc app_name.str.

mysql>SELECT GET_LOCK('lock1',10); 
     -> 1 
mysql>SELECT IS_FREE_LOCK('lock2'); 
     -> 1 
mysql>SELECT GET_LOCK('lock2',10); 
     -> 1 
mysql>SELECT RELEASE_LOCK('lock2'); 
     -> 1 
mysql>SELECT RELEASE_LOCK('lock1'); 
     -> NULL 

Thứ hai RELEASE_LOCK() gọi trả NULL vì khóa 'lock1' được tự động giải phóng bởi thứ hai GET_LOCK() gọi.

Nếu nhiều khách hàng đang chờ khóa, thứ tự mà họ sẽ mua nó không xác định và phụ thuộc vào các yếu tố như thư viện chủ đề đang sử dụng. Đặc biệt, các ứng dụng không nên giả định rằng khách hàng sẽ có được khóa theo thứ tự mà họ đã đưa ra các yêu cầu khóa.

Note
Trước MySQL 5.5.3, nếu một khách hàng cố gắng để có được một khóa mà đã được tổ chức bởi khách hàng khác, nó ngăn chặn theo lập luận timeout. Nếu khách hàng bị chặn chấm dứt, chủ đề của nó không chết cho đến khi yêu cầu khóa hết giờ.

Chức năng này không an toàn để sao chép dựa trên tuyên bố. Bắt đầu với MySQL 5.5.1, một cảnh báo được ghi lại nếu bạn sử dụng chức năng này khi binlog_format được đặt thành STATEMENT. (Bug # 47995)

+0

Tôi đã đọc về GET_LOCK từ tài liệu mysql. Sau đó, tôi đã cố gắng để kiểm tra nó bằng cách thực hiện các mysql SELECT_LOCK SELECT ('lock2', 10); -> 1 hai lần trong cùng một phiên và giả định rằng lệnh gọi thứ hai tới GET_LOCK có cùng tên (lock2) dint chờ cho lần đầu tiên kết thúc. Tôi đã thử nghiệm điều này trong thủ tục lưu trữ của tôi chỉ bằng cách gọi thủ tục được lưu trữ từ hai phiên khác nhau. Nó hoạt động hoàn hảo tốt. Cuộc gọi thứ hai tới GET_LOCK đã chờ cuộc gọi đầu tiên phát hành trước khi hết thời gian chờ. Cảm ơn, eggyal! –

+0

mặc dù cả giải pháp của bạn và Mushu đều giải quyết trường hợp của tôi, tôi chỉ có thể chọn một câu trả lời cho chủ đề câu hỏi này. Tôi đã chọn cái của mình vì nó đơn giản và phù hợp hơn cho trường hợp của tôi. –

+0

xin lỗi tôi vẫn không thể 'upvote', bởi vì tôi không có đủ điểm để làm như vậy. Tôi sẽ trở lại đây khi tôi có đủ điểm. Cảm ơn! –

Các vấn đề liên quan