在门店运营和小型电商场景中,会员积分系统往往不需要庞大的分布式数据库。SQLite凭借零配置、单文件存储和良好的事务支持,成为很多独立开发者搭建会员积分系统的首选。本文以实际落地为目标,从核心表结构设计讲起,逐步扩展到流水记录、过期处理和并发控制,帮助你用SQLite构建一个稳定可用的积分数据库。

核心表结构与字段设计
会员积分系统最基础的两张表是会员表与积分流水表。会员表负责保存用户当前可用积分余额,流水表则记录每一次积分的增减原因和时间。这样的设计遵循数据库范式中的动静分离原则:余额是聚合后的状态,流水是过程数据,二者分开存储既方便对账也便于追溯。
在SQLite中可以使用INTEGER PRIMARY KEY AUTOINCREMENT来定义会员主键,并用CHECK约束保证积分不为负。下面是一个简化的建表语句,其中members表包含会员ID、姓名和当前积分,points_log表通过member_id外键关联会员,并记录变动分数与业务类型。
CREATE TABLE members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
points INTEGER NOT NULL DEFAULT 0 CHECK(points >= 0)
);
CREATE TABLE points_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
member_id INTEGER NOT NULL,
delta INTEGER NOT NULL,
reason TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY(member_id) REFERENCES members(id)
);
上述结构中,points_log的delta字段可以为正也可以为负,正代表积分发放,负代表消耗。通过在应用层同时更新members.points和插入points_log,并在同一个事务中提交,可以避免余额与流水不一致。SQLite默认的事务隔离级别为SERIALIZABLE,在写操作时会锁库,因此非常适合这种强一致要求的积分变动场景。
利用触发器和事务保证数据一致
手动在业务代码里同时维护余额和流水容易遗漏,更稳妥的做法是把逻辑下沉到数据库。SQLite支持AFTER INSERT触发器,我们可以在points_log插入后自动更新members表的积分。这样无论哪个客户端写入流水,余额都会同步变化,减少应用层bug。
下面触发器在插入流水记录后,将对应会员的积分加上delta值。注意这里没有额外判断负数,因为建表时已经用CHECK约束拦截了会导致负余额的更新,若违反约束事务会回滚。
CREATE TRIGGER update_member_points
AFTER INSERT ON points_log
BEGIN
UPDATE members
SET points = points + NEW.delta
WHERE id = NEW.member_id;
END;
在并发兑换积分的请求中,例如两个请求同时扣减同一用户100积分,而余额只有150,若先读后写就会出现超扣。使用触发器配合事务能将“插入流水+更新余额”变成原子操作。应用端只需开启事务执行插入,SQLite的写锁会串行化这些操作,第二个请求在更新余额时触发CHECK失败而回滚,从而自然实现了防止积分透支。相比在代码里加分布式锁,这种方案在单文件库下既简单又可靠。
积分查询与过期清理实践
真实业务常需要统计会员近期积分获取情况,以及清理一年前发放的过期积分。SQLite提供丰富的日期函数,如datetime和date,可以方便筛选时间范围。以下语句查询某会员最近三十天通过消费获得的积分总和。
SELECT SUM(delta) AS earned
FROM points_log
WHERE member_id = 1
AND delta > 0
AND reason = 'purchase'
AND created_at >= datetime('now', '-30 days');
对于积分过期,我们可以在points_log中标记每笔积分的有效期,然后定期运行脚本将过期未用的部分以负流水冲抵。比如每月定时执行:找出一年前发放且未过期的正流水,按用户汇总后插入对应的负delta记录,触发器会自动扣减余额。这种基于流水的过期方式比直接修改余额更容易审计,也避免了误删用户有效积分。
除了功能实现,SQLite文件还应定期备份。由于整个数据库就是一个文件,可用filecopy或VACUUM INTO命令生成快照。对于日活千人的门店系统,这种轻量方案比部署MySQL实例节省大量运维成本,同时借助本文的表结构和触发器,依然能保证积分数据的准确与可追溯。