惱騷php
最近在搞併發的問題,訂單的異步通知和主動查詢會存在併發的問題,用到了Mysql數據庫的 for update 鎖html
在TP5直接經過lock(true),用於數據庫的鎖機制mysql
Db::name('pay_order')->where('order_no',‘S1807081342018949’)->lock(true)->find();
打印生成的SQL語句sql
SELECT * FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE
上面的查詢語句中,咱們使用了 select…for update 的方式,這樣就經過開啓排他鎖的方式實現了悲觀鎖。此時在 pay_order 表中,order_no 爲 S1807081342018949 的那條數據就被咱們鎖定了,其它的事務必須等本次事務提交以後才能執行。這樣咱們能夠保證當前的數據不會被其它事務修改。數據庫
上面咱們提到,使用 select…for update 會把數據給鎖住,不過咱們須要注意一些鎖的級別,MySQL InnoDB默認行級鎖。行級鎖都是基於索引的,若是一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住,這點須要注意。併發
理解悲觀鎖與樂觀鎖oracle
在數據庫的鎖機制中介紹過,數據庫管理系統(DBMS)中的併發控制的任務是確保在多個事務同時存取數據庫中同一數據時不破壞事務的隔離性和一致性以及數據庫的一致性。異步
樂觀併發控制(樂觀鎖)和悲觀併發控制(悲觀鎖)是併發控制主要採用的技術手段。不管是悲觀鎖仍是樂觀鎖,都是人們定義出來的概念,能夠認爲是一種思想。其實不單單是數據庫系統中有樂觀鎖和悲觀鎖的概念,像memcache、hibernate、tair等都有相似的概念。測試
針對於不一樣的業務場景,應該選用不一樣的併發控制方式。因此,不要把樂觀併發控制和悲觀併發控制狹義的理解爲DBMS中的概念,更不要把他們和數據中提供的鎖機制(行鎖、表鎖、排他鎖、共享鎖)混爲一談。其實,在DBMS中,悲觀鎖正是利用數據庫自己提供的鎖機制來實現的。spa
在數據庫中,悲觀鎖的流程以下:
在對任意記錄進行修改前,先嚐試爲該記錄加上排他鎖(exclusive locking)。
若是加鎖失敗,說明該記錄正在被修改,那麼當前查詢可能要等待或者拋出異常。 具體響應方式由開發者根據實際須要決定。
若是成功加鎖,那麼就能夠對記錄作修改,事務完成後就會解鎖了。
其間若是有其餘對該記錄作修改或加排他鎖的操做,都會等待咱們解鎖或直接拋出異常。
如下這句話應用來自:http://www.cnblogs.com/bigfish--/archive/2012/02/18/2356886.html
在oracle中,利用 select * for update 能夠鎖表。假設有個表單products ,裏面有id跟name二個欄位,id是主鍵。
例1: (明確指定主鍵,而且有此筆資料,row lock)
SELECT * FROM products WHERE id='3' FOR UPDATE;
例2: (明確指定主鍵,若查無此筆資料,無lock)
SELECT * FROM products WHERE id='-1' FOR UPDATE;
例3: (無主鍵,table lock)
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
例4: (主鍵不明確,table lock)
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
例5: (主鍵不明確,table lock)
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
注1: FOR UPDATE僅適用於InnoDB,且必須在交易區塊(BEGIN/COMMIT)中才能生效。
注2: 要測試鎖定的情況,能夠利用MySQL的Command Mode ,開二個視窗來作測試。(點開連接,這裏已經有人作個測試了)
先開始一把
使用悲觀鎖的原理就是,當咱們在查詢出 pay_order 信息後就把當前的數據鎖定,直到咱們修改完畢後再解鎖。那麼在這個過程當中,由於 pay_order 被鎖定了,就不會出現其餘操做者來對其進行修改了。
第一次,開啓事務,可是不提交事務
異步通知
-- 開啓事務
START TRANSACTION;
-- 查詢訂單
SELECT id,order_no,`status` FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE;
-- 修改訂單
UPDATE `pay_order` SET `status` = 11 WHERE id = 347;
COMMIT;
-- 查詢數據是否修改爲功
SELECT id,order_no,`status` FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE;
執行結果:很快就執行完畢了,可是數據並無修改爲功(注意:可是重複執行一次,則數據又修改爲功了)
主動查詢
一、加鎖
SELECT id,order_no,`status` FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE;
執行結果,一直在阻塞中
過一會,會自動取消鎖機制
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
二、不加鎖
SELECT id,order_no,`status` FROM `pay_order` WHERE `order_no` = 'S1807081342018949';
執行結果,沒有阻塞,則能正常查詢出數據,不會受第一個事務的影響
第二次,開啓事務,提交事務
異步查詢開啓事務,提交事務
主動查詢加鎖則不受影響
總結:鎖若是是回滾或者提交事務,會自動釋放掉鎖的。
下面研究如下行鎖和表鎖
例1: 明確指定主鍵,而且有此數據,row lock
說明:經過上面的演示,能夠清楚的看到,鎖的是同一個記錄(id = 347),而不是同一個記錄(id = 348)並無受到上一條記錄的影響。
例2: 明確指定主鍵,若查無此數據,無lock
說明:窗口1 查詢結果爲空。窗口2 查詢結果也爲空,查詢無阻塞,說明 窗口1 沒有對數據執行鎖定。
例3:無主鍵,table lock
說明:
窗口1 開啓了事務,查詢訂單號 : order_no = "S1807081342018949",查詢數據正常。
窗口2 也開啓了事務,查詢訂單號 : order_no = "S1807081342018949",查詢阻塞,說明 窗口1 把該記錄給鎖住了(其實這裏表已經被鎖定, 而不是該記錄了)。
窗口3 開啓了事務,查詢訂單號 : order_no = "S1807171712053133",查詢阻塞,說明 窗口1 把該表給鎖住了,不是同一條記錄都不給查啊,阻塞的不要不要的。
只有 窗口1 的記錄回滾或者提交了,窗口2 的查詢阻塞馬上釋放掉了,可是 窗口3 依然在阻塞中(因爲 窗口2 開啓了事務致使的)。同理,回滾或者提交 窗口2 的事務後,窗口3 的記錄也能夠正常查詢了。
例4: 主鍵不明確,table lock
說明:
窗口1 開啓了事務,查詢主鍵 : id > 375 的記錄,查詢數據正常(3條記錄)。
窗口2 也開啓了事務,查詢訂單號 : id > 375 的記錄,查詢阻塞,說明 窗口1 把該記錄給鎖住了(其實這裏表已經被鎖定, 而不是該記錄了)。
窗口3 開啓了事務,查詢訂單號 : id > 376 的記錄,查詢阻塞,說明 窗口1 把該表給鎖住了,不是同一條記錄都不給查啊,阻塞的不要不要的。
只有 窗口1 的記錄提交事務了,窗口2 的查詢阻塞馬上釋放掉了,窗口3 的記錄也能夠正常查詢了。
例5: 主鍵不明確,table lock
select * from pay_order where id<>1 for update;
索引對數據庫的鎖定級別
例6: 明確指定索引,而且有此數據,row lock
mysql> select id,status,order_no from pay_order where status=1 for update;
+------+----------+-------------------+
| id | status | order_no |
|------+----------+-------------------|
| 348 | 1 | S1807081353042055 |
| 349 | 1 | S1807081356043257 |
+------+----------+-------------------+
13 rows in set
Time: 0.003s
注意:上面的字段 status 是創建過索引的
例7: 明確指定索引,若查無此數據,無lock
mysql> select id,status,order_no from pay_order where status=11 for update;
+------+----------+------------+
| id | status | order_no |
|------+----------+------------|
+------+----------+------------+
0 rows in set
Time: 0.001s
參考: