MySQL計數器、每日計數器表設計與調優

計數器

如果應用在表中保存計數器,則在更新計數器時可能碰到併發問題。計數器表在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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章