mysql 计数器实现

2020-04-22  本文已影响0人  尹楷楷

或为辽东帽,清操厉冰雪。

如果应用在表中保存计数器,则在更新计数器时可能,碰到并发问题。计数器表在web应用中很常见。可以用这种表缓存一个用户的朋友数、文件下载次数等。

问题在于,若只有一条记录保存计数器数量,那么在并发修改事务下因为需要杜绝线程安全问题,INNODB行锁在起作用。这样导致修改的事务引发阻塞、串行执行,进而性能下降。为了解决这个问题我们使用一种以空间换时间 的思想。

我们创建一张计数器表,包含 slot 槽标识、cnt 数量字段

CREATE TABLE `test`.`hit_counter`  (
  `slot` tinyint(100) NOT NULL COMMENT '槽',
  `cnt` int(255) UNSIGNED NOT NULL COMMENT '数量',
  PRIMARY KEY (`slot`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

然后预先在这张表中插入100行数据作为槽(slot)。编写存储过程,插入100条数据。

CREATE DEFINER=`root`@`%` PROCEDURE `insert_hit_counter`(in start int(10),in max_num int(10))
begin
 declare i int default 0;
 /*把autocommit设置成0*/
 set autocommit= 0;
 repeat
 set i=i+1;
 INSERT INTO `test`.`hit_counter`(`slot`, `cnt`) VALUES ((start+i), 0);
 until i=max_num end repeat;
commit;
end

CALL insert_hit_counter(0,100);

现在选择一个随机的槽(slot)进行更新,在应用程序中。即是出现并发修改,这样可不会引发阻塞了。

set @slot = FLOOR( 1 + RAND() * (101 - 1));
UPDATE hit_counter SET cnt = cnt +1  WHERE slot = @slot;

被本来我是直接使用 UPDATE hit_counter SET cnt = cnt +1 WHERE slot = FLOOR( 1 + RAND() * (101 - 1)); 语句的,但是存在问题。有时候影响行数0,有时影响函数为2。很奇怪;所以这里使用用户变量来存储随机数。

使用下面SUM聚合查询即可获得统计结果

SELECT SUM(cnt) FROM hit_counter;
每天重新开始一个新的计数器

设计表如下

CREATE TABLE `test`.`hit_counter`  (
  `slot` tinyint(100) NOT NULL COMMENT '槽',
  `cnt` int(255) UNSIGNED NOT NULL COMMENT '数量',
  `day` date NOT NULL COMMENT '天',
  PRIMARY KEY (`slot`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

使用 ON DUPLICATE KEY UPDATE 插入或更新如下

set @slot = FLOOR( 1 + RAND() * (101 - 1));
INSERT INTO `test`.`hit_counter`(`slot`, `cnt`, `day`) VALUES (@slot,1, CURRENT_DATE) ON DUPLICATE KEY UPDATE cnt = cnt + 1;

按照时间过滤统计计数如下

SELECT SUM(cnt) FROM hit_counter WHERE day = CURRENT_DATE;

如果希望减少函数,以避免表变得太大,可以写一个周期执行的任务,合并所有的结果到0号槽,并且删除所有其他的槽:

UPDATE hit_counter AS c
INNER JOIN (
  SELECT day,sum(cnt) AS cnt,MIN(slot) AS MSLOT
    FROM 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 );

DELETE FROM hit_counter WHERE slot!= 0 AND cnt = 0;

事实上,这个计数器功能使用redis实现会很方便。也就是一个命令的事情。。。

上一篇 下一篇

猜你喜欢

热点阅读