計數器
如果應用在表中保存計數器,則在更新計數器時可能碰到併發問題。計數器表在Web應用中很常見。可以用這種表緩存一個用戶的朋友數、文件下載次數等。創建一張獨立的表存儲計數器通常是個好主意,這樣可使計數器表小且快。使用獨立的表可以幫助避免查詢緩存失效,並且可以使用本節展示的一些更高級的技巧。
應該讓事情變得儘可能簡單,假設有一個計數器表,只有行數據,記錄網站的點擊次數:
CREATE TABLE hit_counter (
cnt INT UNSIGNED NOT NULL
) ENGINE = INNODB;
網站的每次點擊都會導致對計數器進行更新:
UPDATE hit_counter SET cnt = cnt+ 1;
問題在於,對於任何想要更新這一行的事務來說,這條記錄上都有一個全局的互斥鎖(mutex)。這會使得這些事務只能串行執行。要獲得更高的併發更新性能,也可以將計數器保存在多行中,每次隨機選擇一行進行更新。這樣做需要對計數器表進行如下修改:
CREATE TABLE hit_counter (
slot TINYINT UNSIGNED NOT NULL PRIMARY KEY,
cnt INT UNSIGNED NOT NULL
) ENGINE = INNODB;
然後預先在這張表增加100行數據。現在選擇一個隨機的槽 (slot) 進行更新:
UPDATE hit_counter SET cnt = cnt + 1 WHERE slot = RAND() * 100;
要獲得統計結果,需要使用下面這樣的聚合查詢:
SELECT SUR(cnt) FROM hit_counter;
每日計數器
另外一個常見的需求是每隔一段時間開始一個新的計數器(例如,每天一個)。如果需要這麼做,則可以再簡單地修改一下表設計:
CREATE TABLE dally_hit_counter (
day DATE not null,
slot TINYINT UNSIGNED NOT NULL,
cnt INT UNSIGNED NOT NULL,
primary key(day, slot)
)ENGINE = INNODB;
在這個場景中會,可以不用像前面的例子那樣預先生成行,而用ON DUPLICATE KEY UPDATE進行代替
INSERT INTO `dally_hit_counter` ( DAY, slot, cnt )
VALUES
( CURRENT_DATE, RAND( ) * 100, 1 )
ON DUPLICATE KEY UPDATE cnt = cnt + 1;
如果希望減少表的行數,以避免表變得太大,可以寫一個週期執行的任務,合併所有結果到0號槽,並且刪除所有的槽。
UPDATE daily_hit_counter as c
INNER JOIN (
SELECT day, SUM(cnt) as cnt, MIN(slot) as mslot
FROM daily_hit_counter
GROUP BY day
) AS x USING(day)
SET c.cnt = IF(c.slot = x.mslot, x.cnt, 0),
c.slot = IF(c.slot = x.mslot, 0, c.slot);
內容參考自《高性能MySQL》 P135