2012-08-22 96 views
4

TL; DR - MySQL不允許您鎖定表並同時使用事務。有沒有辦法解決?MySQL:如何鎖定表並啓動事務?

我有一個MySQL表我用來緩存一些(慢)外部系統的數據。這些數據用於顯示網頁(用PHP編寫)。每隔一段時間,當緩存的數據被認爲太舊時,其中一個網絡連接應該會觸發對緩存數據的更新。

有三個問題,我不得不面對:

  • 其他客戶端將嘗試讀取緩存中的數據,而我更新它
  • 多個客戶端可以決定緩存的數據太舊,並嘗試同時
  • PHP的情況下做的工作可以在任何時候意外終止更新,數據不應該被破壞

我能解決的第一個和最後通過使用事務處理問題,因此客戶將能夠讀取舊數據,直到事務提交,並立即看到新數據。任何問題只會導致事務回滾。

我可以通過鎖定表來解決第二個問題,以便只有一個進程有機會執行更新。當其他進程獲得鎖定時,他們會意識到他們已經被擊敗了,並且不需要更新任何東西。

這意味着我需要同時鎖定表開始交易。根據MySQL手冊,this is not possible。啓動事務將釋放鎖,並鎖定表提交任何活動的事務。

有沒有辦法解決這個問題,還是有另一種方法來完成我的目標?

回答

4

如果是我,我會使用MySQL中的advisory locking function來實現更新緩存的互斥鎖和讀取隔離的事務。例如

begin_transaction(); // although reading a single row doesnt really require this 
$cached=runquery("SELECT * FROM cache WHERE key=$id"); 
end_transaction(); 

if (is_expired($cached)) { 
    $cached=refresh_data($cached, $id); 
} 
... 

function refresh_data($cached, $id) 
{ 
$lockname=some_deterministic_transform($id); 
if (1==runquery("SELECT GET_LOCK('$lockname',0)") { 
    $cached=fetch_source_data($id); 
    begin_transaction(); 
    write_data($cached, $id); 
    end_transaction(); 
    runquery("SELECT RELEASE_LOCK('$lockname')"); 
} 
return $cached; 
} 

(順便說一句:如果你嘗試這個具有持久連接不好的事情可能發生)

+0

謝謝,這似乎工作得很好! – Malvineous

0

第二個問題可以根本不涉及數據庫來解決。爲緩存更新過程提供一個鎖定文件,以便其他客戶端知道有人已經在其上。這可能無法捕捉到每一個角落的情況,但是如果兩個客戶端同時更新緩存,這是否成功?畢竟,他們正在對緩存進行更新的事務仍然是一致的。

甚至可以通過將最後一次緩存更新時間存儲在表中來實現鎖定。當客戶想要更新緩存時,使其鎖定該表,檢查上次更新時間,然後更新該字段。

即,實現您自己的鎖定機制以防止多個客戶端更新緩存。交易將負責其餘部分。

+0

不幸的是鎖定文件在這裏不起作用,因爲我們有多個網站服務器運行本地站點文件副本。所以一個Web服務器將看不到另一個創建的鎖文件。我將最後一次更新時間存儲在一個表中,但在緩存刷新完成之前無法更新 - 否則,如果時間更新並且進程被終止(每點3),數據將不會被刷新,但最後更新時間將錯誤地說是。將整個表鎖定爲整個更新將斷開點1,阻止其他客戶端在刷新期間讀取舊數據。 – Malvineous

5

這意味着我需要在兩個鎖表,並開始交易

這是怎麼了你可以這樣做:

SET autocommit=0; 
LOCK TABLES t1 WRITE, t2 READ, ...; 
... do something with tables t1 and t2 here ... 
COMMIT; 
UNLOCK TABLES; 

欲瞭解更多信息,請參閱mysql doc

+0

這看起來確實會起作用,但只適用於InnoDB表(如果這是一個問題)。 – Malvineous