推荐用三张表(users、tags、user_tag_relations)存储标签,关联表需建联合唯一索引和tag_id索引;动态打标应采用规则引擎+异步处理;精准推送宜用INNER JOIN多表关联而非子查询,并结合游标分页与Redis缓存。

用户标签怎么存才不拖慢查询
标签体系一旦变多,用 SELECT * FROM users WHERE tags LIKE '%vip%' 这种方式查,很快会卡死。根本原因是模糊匹配无法走索引,且标签堆在单字段里没法原子化更新或统计。
推荐拆成三张表:users(用户主表)、tags(标签字典)、user_tag_relations(关联表)。关联表必须有联合唯一索引 (user_id, tag_id),还要单独建 tag_id 索引——否则按标签反查用户时会全表扫描。
常见错误:把标签存在 JSON 字段里,看似灵活,但 MySQL 5.7+ 的 JSON_CONTAINS() 查询依然比关联表慢 3–5 倍,而且没法做标签热度排序、交集计算等聚合操作。
动态打标逻辑该放在哪一层
不能全靠前端传 is_vip=1、last_login_days=3 这类原始字段再由 PHP 拼规则——规则一变就得改代码、重跑历史数据,还容易漏。
立即学习“PHP免费学习笔记(深入)”;
建议用「规则引擎 + 异步打标」组合:
- 规则配置存数据库,字段如
rule_name、condition_sql(如WHERE last_login_time > DATE_SUB(NOW(), INTERVAL 7 DAY))、tag_id - 用户关键行为(登录、下单、点击)触发
update_user_tags($user_id),只做轻量级标记(比如写入 Redis 队列) - 后台用
php artisan tag:sync定时消费队列,执行 SQL 规则批量更新user_tag_relations
注意:不要在 HTTP 请求里同步执行复杂规则 SQL,尤其涉及 JOIN 多张大表时,超时风险极高。
精准推送时怎么高效取带标签的用户
推送不是“找出所有 VIP 用户”,而是“找出最近 7 天登录过、且是 VIP、且未收到今日优惠短信的用户”。这种多条件组合,硬拼 IN 子查询或临时表极易锁表。
实操要点:
- 用
INNER JOIN关联user_tag_relations多次(每次对应一个标签),比用GROUP BY user_id HAVING COUNT(DISTINCT tag_id) = 2快得多 - 给推送任务加
limit 1000和offset分页,避免深分页;更稳的做法是用WHERE id > ?游标分页 - 如果推送频次高,把常用标签组合结果缓存到 Redis Hash,键名如
push:segment:vip_recent7,值为用户 ID 列表,TTL 设为 1 小时
性能陷阱:用 array_intersect() 在 PHP 里合并多个标签的用户数组,10 万用户起步就内存溢出;必须把交集逻辑下推到数据库或 Redis。
PHP 实现打标时绕不开的细节
user_tag_relations 表里,tag_id 是外键,但别设 ON DELETE CASCADE——删标签时误删大量关联记录,恢复成本极高。改成软删除 + 定期归档更安全。
打标动作要幂等:INSERT IGNORE INTO user_tag_relations (user_id, tag_id) VALUES (?, ?) 或先 SELECT 再判断,避免重复插入报唯一键冲突。
标签命名必须规范,禁止用空格、中文、特殊符号:$tagSlug = preg_replace('/[^a-z0-9_]/', '', strtolower($input)),否则后续生成 SQL 或 Redis 键名会出错。
最常被忽略的一点:标签变更后,老的推送任务还在用旧快照。得在推送前加校验,比如读取 tags_updated_at 时间戳,比任务创建时间早就跳过这条。



















