我盡力去完成看似簡單,競爭條件
Db的類型:MyISAM數據
表結構:card_id的,狀態
查詢:從表中選擇一個未使用的card_id的,並設置行作爲「用過的」。
是它的競爭條件,當更新狀態前兩個查詢在同一時間運行,並且,同樣是card_id的兩次取?
我做了一些搜索了。看起來鎖表是一個解決方案,但它對我來說是矯枉過正的,需要鎖特權。
任何想法?
謝謝!
我盡力去完成看似簡單,競爭條件
Db的類型:MyISAM數據
表結構:card_id的,狀態
查詢:從表中選擇一個未使用的card_id的,並設置行作爲「用過的」。
是它的競爭條件,當更新狀態前兩個查詢在同一時間運行,並且,同樣是card_id的兩次取?
我做了一些搜索了。看起來鎖表是一個解決方案,但它對我來說是矯枉過正的,需要鎖特權。
任何想法?
謝謝!
這真的取決於你正在運行的語句。
對於MyISAM表中普通的舊的UPDATE
語句,MySQL將在整個表上獲得一個鎖,所以兩個會話之間沒有「競爭」條件。一個會話將等待,直到鎖定被釋放,然後繼續它自己的更新(或將等待指定的時間,並以「超時」中止。)
但是,如果你問的是兩個會話既針對表運行SELECT,又檢索要更新的行的標識符,並且兩個會話檢索相同的行標識符,然後兩個會話試圖更新同一行,那麼是的,這是一個明確的可能性,真的不得不考慮。
如果條件不解決,那麼它基本上會是「最後更新勝」的問題,在第二屆會議將(可能)覆蓋由先前的更新所做的更改。
如果這對於您的應用程序來說是不成熟的情況,那麼需要使用不同的設計來解決這個問題,或者使用一些機制來防止第二次更新覆蓋第一次更新所應用的更新。
正如您所提到的,一種方法是通過首先獲得對錶的排他鎖(使用LOCK TABLES語句),然後運行SELECT獲取標識符,然後運行UPDATE以更新確定行,然後終於解除鎖定(使用UNLOCK TABLES語句。)
這對於一些低量,低併發應用的一個可行的辦法。但它確實有一些明顯的缺點。主要關心的是併發性的降低,這是由於在單個資源上獲得的排它鎖,這可能導致性能瓶頸。
另一種方法是所謂的「樂觀鎖定」的戰略。 (與之前描述的可能被描述爲「悲觀鎖定」的方法相反)。
對於「樂觀鎖定」策略,將額外的「計數器」列添加到表中。每當將更新應用於表中的一行時,該行的計數器就會加1。
要使用此「計數器」列,當查詢檢索將要(或可能)稍後更新的行時,該查詢還會檢索計數器列的值。
當UPDATE嘗試,語句也是「計數器」列的當前值的行與所述計數器列的先前檢索值進行比較。 (我們只是有一個謂語(如UPDATE語句的WHERE子句)的,例如,
UPDATE mytable
SET counter = counter + 1
, col = :some_new_value
WHERE id = :previously_fetched_row_identifier
AND counter = :previously_fetched_row_counter
如果其他會話已應用的更新,我們正試圖更新(時間之間的某個行我們會議檢索行和之前我們會嘗試做更新),然後在該行的「計數器」列中的值將被改變。
謂語我們的UPDATE語句檢查的是,如果「計數器」已經改變,這會導致我們的更新不被應用,然後我們可以檢測到這種情況(即受影響的行數將是0而不是1),我們的會話可以採取一些適當的行動。 !有些其他呃會話更新我們打算更新行!「)
有關於如何實現的一些好寫起坐‘樂觀鎖定’策略。
一些ORM框架(例如Hibernate,JPA)爲這種類型的鎖定策略提供支持。
不幸的是,MySQL不會在UPDATE語句提供了一個returning子句的支持,如:
UPDATE ...
SET status = 'used'
WHERE status = 'unused'
AND ROWNUM = 1
RETURNING card_id INTO ...
其他RDBMS(如Oracle)的辦提供這種功能。利用UPDATE語句的這一特性,我們可以簡單地運行UPDATE
語句來查找1)找到一行status = 'unused'
,2)更改status = 'used'
的值,3)返回該行的card_id
(或任何我們想要的列)我們剛剛更新。
,圍繞其運行SELECT,然後運行一個單獨的更新,與其他一些會議上更新我們的選擇和我們的UPDATE之間的行的潛在的問題得到。
但RETURNING
子句不能在MySQL的支持。而且我還沒有找到任何可靠的方式來在MySQL中模擬這種類型的功能。
這對於工作,你
我不完全知道爲什麼我放棄了以前使用的用戶變量這種方法(我在上面,我曾與此玩耍了提及。我想也許我需要更一般的東西,它會更新多行並返回一組id值;或者,可能有些東西不能保證用戶變量的行爲(然後,我只是仔細地引用用戶變量SELECT語句;我沒有在DML中使用用戶變量;這可能是因爲我沒有保證它們的行爲。)
既然你有興趣只有一個排,三所陳述這個序列可以爲你工作:
SELECT @id := NULL ;
UPDATE mytable
SET card_id = (@id := card_id)
, status = 'used'
WHERE status = 'unused'
LIMIT 1 ;
SELECT ROW_COUNT(), @id AS updated_card_id ;
重要的是,這三個語句在同一個數據庫會話中運行(即保留數據庫會話;不要放棄它,並得到一個新的。)
首先,我們初始化用戶變量(@id
)這是我們不會從表中真正的價值card_id的混淆值。 (A SET @id := NULL
聲明將工作爲好,沒有返回結果,如SELECT語句一樣。)
接下來,我們運行UPDATE
聲明:1)找到一排,其中status = 'unused'
; 2)改變status
列的值到'used'
,以及3)將@id
用戶變量的值設置爲我們更改的行的card_id
值。 (我們希望card_id
列是整數類型,而不是字符,以避免任何可能的字符集轉換問題。)
接下來,我們運行一個查詢獲取由前一個UPDATE語句更改的行數,使用ROW_COUNT()
函數(我們將需要驗證,這是1上的客戶端),並檢索@id
用戶變量,這將是從該改變該行的值card_id的的值。
感謝您的詳細回覆! –
很難在這裏添加長評論,所以我必須回答我的問題。請檢查我的答案併發布您的想法。謝謝! –
後,我張貼此問題,我想到了一個解決方案,它是完全一樣的,你在最後提到的一個。我使用update語句,它是「update TABLE set status ='used'where status ='unused'limit 1」,它返回TABLE的主Id,然後我可以使用這個主ID來獲取cart_id。就像你說的那樣,同時發生兩個更新語句,「MySQL將獲得整個表的鎖定,所以兩個會話之間不存在」競爭「狀況,所以這應該解決我的問題。但我不確定你爲什麼這麼說,「MySQL不提供對樣式聲明的支持」。
「UPDATE」語句的返回值是該語句影響的行數。在UPDATE語句後面對'mysql_info'(C API)函數的調用會給出一個字符串,其中包含匹配的行數,更改的行數以及警告的數量。如果你有一些機制讓MySQL返回受影響的行的主鍵值,我會非常感興趣的看到這一點,因爲我有幾種情況,這將是非常有用的。 – spencer7593
我重寫了一下我的答案。當我說「MySQL不提供支持」時,我特別提到了UPDATE語句的RETURNING子句,我們在Oracle等人中支持該語句。 'UPDATE t SET c = 1 WHERE c = 0 LIMIT 1'形式的聲明完全符合你的要求,除了你沒有任何可靠的方法來確定哪一行是更新的。 (我用一些用戶變量和timestamp列來演示一些東西,但我還沒有找到一種可靠,簡單的識別行的方法。 – spencer7593
爲了避免正常db用戶的'LOCK PRIVILEGE',你可以使用'STORED PROCEDURE'來執行此操作,因爲你可以切換用戶上下文執行 –
我更新了我的答案;我提供了一個使用用戶變量來返回已更新行的'cardid'值的示例。 – spencer7593