2019獨角獸企業重金招聘python工程師標準》
問題背景:
假設mysql資料庫有一張會員表vip_member(innodb表),結構如下:
當乙個會員想續買會員(只能續買1個月、3個月或6個月)時,必須滿足以下業務要求:
問題分析:
對於上面這種情況,我們一般會先select查出這條記錄,然後根據查出記錄的end_at再update start_at和end_at,偽**如下(為uid是1001的會員續1個月):
vipmember = select * from vip_member where uid=1001 limit 1 # 查uid為1001的會員
if vipmember.end_at
update vip_member set start_at=now(), end_at=date_add(now(), interval 1 month), active_status=1, updated_at=now() where uid=1001
else:
update vip_member set end_at=date_add(end_at, interval 1 month), active_status=1, updated_at=now() where uid=1001
假如同時有兩個執行緒執行上面的**,很顯然存在「資料覆蓋」問題(即乙個是續1個月,乙個續2個月,但最終可能只續了2個月,而不是加起來的3個月)。
解決方案:
a、我想到的第一種方案是把select和update合成一條sql,如下:
update vip_member
set
start_at = case
when end_at
then now()
else start_at
end,
end_at = case
when end_at
then date_add(now(), interval 1 month)
else date_add(end_at, interval 1 month)
end,
active_status=1,
updated_at=now()
where uid=#uid:bigint#
limit 1;
so easy!
b、第二種方案:事務
,即用乙個事務來包裹上面的select+update操作。
那麼是否包上事務就萬事大吉了呢?
顯然不是。因為如果同時有兩個事務都分別select到相同的vip_member記錄,那麼一樣的會發生資料覆蓋問題。那有什麼辦法可以解決呢?難道要設定事務隔離級別為serializable,考慮到效能不現實。
我們知道innodb支援行鎖。
檢視mysql官方文件(
innodb locking reads
)了解到innodb在讀取行資料時可以加兩種鎖:讀共享鎖和寫獨佔鎖。
讀共享鎖是通過下面這樣的sql獲得的:
select * from parent where name = 'jones' lock in share mode;
如果事務a獲得了先獲得了讀共享鎖,那麼事務b之後仍然可以讀取加了讀共享鎖的行資料,但必須等事務a commit或者roll back之後才可以更新或者刪除加了讀共享鎖的行資料。
寫獨佔鎖是通過select...for update獲得:
select counter_field from child_codes for update;
update child_codes set counter_field = counter_field + 1;
如果事務a先獲得了某行的寫獨佔鎖,那麼事務b就必須等待事務a commit或者roll back之後才可以訪問行資料。
顯然要解決會員狀態更新問題,不能加讀共享鎖,只能加寫獨佔鎖,即將前面的sql改寫成如下:
vipmember = select * from vip_member where uid=1001 limit 1 for update # 查uid為1001的會員
if vipmember.end_at
update vip_member set start_at=now(), end_at=date_add(now(), interval 1 month), active_status=1, updated_at=now() where uid=1001
else:
update vip_member set end_at=date_add(end_at, interval 1 month), active_status=1, updated_at=now() where uid=1001
c、第三種方案:樂觀鎖,類cas機制
第二種加鎖方案是一種悲觀鎖機制。而且select...for update方式也不太常用,聯想到cas實現的樂觀鎖機制,於是我想到了第三種解決方案:樂觀鎖。
具體來說也挺簡單,首先select sql不作任何修改,然後在update sql的where條件中加上select出來的vip_memer的end_at條件。如下:
vipmember = select * from vip_member where uid=1001 limit 1 # 查uid為1001的會員
cur_end_at = vipmember.end_at
if vipmember.end_at
update vip_member set start_at=now(), end_at=date_add(now(), interval 1 month), active_status=1, updated_at=now() where uid=1001 and end_at=cur_end_at
else:
update vip_member set end_at=date_add(end_at, interval 1 month), active_status=1, updated_at=now() where uid=1001 and end_at=cur_end_at
這樣可以根據update返回值來判斷是否更新成功,如果返回值是0則表明存在併發更新,那麼只需要重試一下就好了。
方案比較:
三種方案各自優劣也許眾說紛紜,只說說我自己的看法:
php中mysql函式 php中mysql有關函式
1.mysql query 一般是用來查詢資料裡面的資料。如 username post name sql select from members where login name username result mysql query sql 以上程式是檢測資料庫中是否存在表單傳送過來的使用者名稱...
mysql中 變數 mysql中的變數
toc 變數 mysql本質是一種程式語言,需要很多變數來儲存資料。mysql中很多的屬性控制都是通過mysql中固有的變數來實現的。系統變數 系統內部定義的變數,系統變數針對所有使用者 mysql客戶端 有效。檢視系統所有變數 show variables like pattern mysql允許...
mysql中 變數 MYSQL中的變數 MySQL
bitscn.com 只記很基礎的知識,細節東西太麻煩了,而且我也用不到。變數分為使用者變數與系統變數。使用者變數 使用者變數與資料庫連線有關,在這個連線中宣告的變數,在連線斷開的時候,就會消失。在此連線中宣告的變數無法在另一連線中使用。使用者變數的變數名的形式為 varname的形式。名字必須以 ...