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 SUM(cnt)
FROM hit_counter;

一个常见的需求是每隔一段时间开始一个新的计数器(例如,每天一个)。如果需要这么做,则可以再简单地修改一下表设计:

CREATE TABLE daily_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 daily_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
        GRUOP 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);

DELETE FROM daily_hit_counter WHERE slot <> 0 AND cnt = 0;

更快地读,更慢地写

为了提升读查询的速度,经常会需要建一些额外所有,增加冗余列,甚至是创建缓存表和汇总表。这些方法会增加写查询的负担,也需要额外的维护任务,但在设计高性能数据库时,这些都是常见的技巧:虽然写操作变得更慢了,但更显著地提高了读操作的性能。

然而,写操作变慢并不是读操作变得快所付出的唯一代价,还可能同时增加了读操作和写操作的开发难度。

猜你喜欢

转载自blog.csdn.net/weixin_42570248/article/details/89788431
今日推荐