如何实现大量的标签匹配?

环境#

mysql: 8.0.32
php: 8.1
laravel: 10.x

问题#

按月分表单月 200W 的数据量,标签 tags 字段需要做标签的精确匹配运算,标签有 4 级的分类,且分类可以动态变动。
目前用 mysql 的 json 字段存储了 tags,建立多值索引,利用 json_overlaps 函数进行查询,在目标匹配数量较小的情况下(如不超过 10 个),查询速度还在可以忍受的范围内。
目前的问题是,tags 存在分类,标签在第 4 级。需求层面可以多选 1,2,3 级分类查询,且分类能够动态变动,所以数据库没有存分类 id,多选上级分类时,统一转化为 4 级标签进行匹配。
造成 json_overlaps 的目标数量达到几十甚至上百个,查询效率低下。

目前考虑过:

  1. 因为分类可以动态变动,如果存储分类 id,一旦分类发生变动会有极大的更新历史数据的负担。
  2. 将数据和 tag 及分类的关系展开存关系表然后 join。需求层面的查询条件导致无论左右表谁做驱动表,结果都会出现几十万的结果条数,join 之后 nested loop 行数太多还是很慢。
  3. 有人提议用 elasticsearch 来做,一方面项目需要采购和额外开发,成本略高;另一方面缺乏 elasticsearch 使用经验也不确定是否可以实现,还有一定的学习成本。目前还是考虑优先基于 mysql 实现,实在不行的话再做其它考虑。

补充下大致的表结构:

CREATE TABLE `t_tags` (
  `id` int NOT NULL AUTO_INCREMENT,
  `tag_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标签名称',
  `parent_id` int NOT NULL DEFAULT '0' COMMENT '父级id',
  `tag_level` tinyint DEFAULT '1' COMMENT '层级',
  `id_path` varchar(64) DEFAULT NULL COMMENT 'id层级路径',
  `name_path` varchar(64) DEFAULT NULL COMMENT '名称层级路径',
  `tag_type` tinyint DEFAULT '1' COMMENT '类型:1分类,2标签',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='标签表'

CREATE TABLE `t_records` (
  `id` int NOT NULL AUTO_INCREMENT,
  `tag_list` json DEFAULT null COMMENT '命中标签集合',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='记录表'

所谓动态变动是指:
1、标签会增加新的,原有的标签也可能不再使用;
2、原有的标签和分类的从属关系可能会变更;
3、可能会增加新的分类或删除原有的分类

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 16
巴啦啦

你说的如果存储类型 id,动态变更,会引起更新历史数据的负担,这一点不是很明白,能说下场景吗? 历史数据是已经形成的,不应该去更改,应该要冗余标签名称。修改分类名称,不应该去改变原有涵义,只能修改为换一个说法,比如显示屏改为显示器。不应该显示屏改为枕头。如果需要枕头,那应该新增,删除分类,也应该只是隐藏。你分了表,最好冗余一张主表,方便查询。
你看我理解得对不对,你的结构是 4 级分类,每个商品,可以拥有任意一级的多个标签,即你存储标签的字段,一条记录可能存储着多个一级分类标签~多个四级分类标签。这些都在同一个数据库字段内。是这样吗?

1年前 评论
anjing 1年前
穿过你的黑发的我的手 (楼主) 1年前
穿过你的黑发的我的手 (楼主) 1年前
穿过你的黑发的我的手 (楼主) 1年前
巴啦啦 (作者) 1年前
穿过你的黑发的我的手 (楼主) 1年前
巴啦啦 (作者) 1年前
巴啦啦

你把标签,全部丢到 redis 位图里面,标签 id 为键,商品 id 为位数 (假设你们的是商品),每形成一个商品或记录,就用商品或记录 id,设置到位图对应的位置。也就是一个商品或记录的新增,将可能会对多个位图进行操作。需要多标签查询的时候,使用 bittop 命令把相关标签的位图做与操作,这样可以把拥有这些商品标签的商品或记录查询出来。你看这样行不

1年前 评论
穿过你的黑发的我的手 (楼主) 1年前
Complicated

没说的,你这量,最好还是 es

1年前 评论

es 使用 keyword 把, 匹配效率很高

1年前 评论

用 clickhouse 吧。我就有你这种需求,直接算完存 clickhouse 了

1年前 评论
穿过你的黑发的我的手 (楼主) 1年前
哪吒的狗腿子 (作者) 1年前