mysql如何实现积分系统设计_mysql业务项目解析

5次阅读

积分变更必须用事务包裹并配合乐观锁,流水表需含 trace_id、前后余额等字段,Redis 缓存余额并异步双写,过期清理须分批避峰。

mysql 如何实现积分系统设计_mysql 业务项目解析

积分增减必须用事务包裹,否则并发写入会丢分

用户做签到、下单、评价等行为时,积分变动常伴随其他业务操作(如更新订单状态)。若不加事务,高并发下 SELECT …… FOR UPDATE 缺失或 UPDATE 未隔离,会出现超发或漏扣。比如两个线程同时读取用户当前积分为 100,各自加 10 后写回 110,实际应为 120。

  • 所有积分变更操作必须在 BEGIN TRANSACTION 内完成,且事务粒度尽量小
  • 优先使用 UPDATE user_points SET points = points + ? WHERE user_id = ? AND version = ? 配合乐观锁(version 字段),比悲观锁更轻量
  • 避免先 SELECTUPDATE 的两步写法;如必须查后改,务必加 SELECT …… FOR UPDATE 且确保走主键或唯一索引
  • 注意 MySQL 默认隔离级别是 REPEATABLE READ,但无法防止“幻读”场景下的计数偏差,积分流水表插入和余额更新需在同一事务中完成

积分流水表要带业务单号 + 类型 + 幂等键,不可只存净变化

单纯记录“+10 分”无法追溯来源,也无法对账或回滚。每条积分变动必须对应一次明确的业务动作,并支持重放与校验。

  • point_log 表至少包含:id(自增)、user_idorder_no(如订单号 / 活动 ID)、type(枚举:sign_in、order_pay、refund、admin_adjust)、amount(正负整数)、before_balanceafter_balancetrace_id(全局幂等键)
  • trace_id 必须由上游业务生成并保证唯一(如 order_pay_20240520123456789),插入前用 INSERT …… ON DUPLICATE KEY UPDATE 防重
  • 不要省略 before_balanceafter_balance —— 它们是核对最终余额是否一致的关键依据,排查问题时比翻日志快得多
CREATE TABLE point_log (id BIGINT PRIMARY KEY AUTO_INCREMENT,   user_id BIGINT NOT NULL,   order_no VARCHAR(64) DEFAULT '',   type TINYINT NOT NULL COMMENT'1= 签到,2= 支付,3= 退款,4= 后台调整 ',   amount INT NOT NULL,   before_balance INT NOT NULL,   after_balance INT NOT NULL,   trace_id VARCHAR(128) NOT NULL UNIQUE,   created_at DATETIME DEFAULT CURRENT_TIMESTAMP,   INDEX idx_user_created (user_id, created_at),   INDEX idx_trace (trace_id) ) ENGINE=InnoDB;

余额查询不能直连主库,要用缓存兜底 + 异步双写

用户首页频繁查积分,若每次都查 user_points 表,主库压力大,且一旦慢查询拖垮连接池,整个登录链路都会卡住。

  • 用户积分余额必须写入 Redis,结构用 hash:键为 user:points:{user_id},字段为 balanceversion
  • 每次积分变更后,MySQL 更新成功 → 异步发 MQ 消息 → 消费端更新 Redis;Redis 更新失败要告警,但 ** 不阻塞主流程 **
  • 前端 查积分时,优先读 Redis;若 Redis 为空或异常,则降级查 MySQL 并回填缓存(注意加锁防击穿)
  • 禁止在事务中同步调用 Redis 写操作 —— 网络延迟或 Redis 故障会导致事务长时间挂起甚至超时

过期积分要分批清理,别用 DELETE FROM … WHERE expired_at

全表扫描删过期积分,锁表时间长、IO 压力大,高峰期可能直接拖垮主库。

  • 按月分表存储过期规则(如 point_expired_202404),或在流水表加 expired_at 字段并建联合索引 (user_id, expired_at)
  • 用游标分页清理:DELETE FROM point_log WHERE id,每次取最大 id 作为下次起点
  • 清理任务必须避开业务高峰,并监控 Rows_affected 和执行耗时;单次超过 2 秒应暂停几秒再继续
  • 过期逻辑建议放在应用层判断(如查余额时过滤已过期部分),数据库只做物理归档,不参与实时计算

真实项目里最常被忽略的是流水表的 trace_id 设计和 Redis 回填时的并发竞争——这两处出问题,基本意味着积分对不上,而且很难定位。

星耀云
版权声明:本站原创文章,由 星耀云 2026-01-05发表,共计1902字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources