ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于MySQL的食谱菜谱数据库设计与检索实践

基于MySQL的食谱菜谱数据库设计与检索实践 简介《食谱菜谱大全》是一份收录4927条菜式信息的数据库资源涵盖菜名、做法、功效、烹饪时间与烹饪方式等字段面向烹饪爱好者、数据分析师、Web开发者以及营养健康研究者适用于菜谱检索、菜系统计、健康饮食筛选、推荐系统构建等多种场景。资源以ZIP压缩包形式发布内含4个文件分别提供SQL、JSON、CSV、XLSX四种格式SQL适合关系型数据库管理与复杂查询JSON便于前后端数据交换CSV可直接导入Excel做数据清洗和报表XLSX则适合公式计算与可视化分析整套数据体积约10.35MB用户可根据工具链选择最合适的格式。目前已有4690人学习下载。利用这份数据不仅可以分析不同菜系的分布与地域饮食特色对比烹饪时间与技法以寻找高效方案还能基于功效字段探索对特定健康需求的适配菜品或为美食推荐算法提供基础数据开发者也可以将其集成到菜谱搜索引擎、在线教学平台、健康食谱App等应用中快速完成菜品数据层建设。1. 食谱菜谱大全数据库到底是存什么菜谱不只是文本如果你的项目名字叫「食谱菜谱大全数据库」要做的往往不是把几千篇菜谱文档堆成一个文件夹而是把菜、食材、分类、步骤拆成一套能查询、能统计、能对接小程序和后台的关系表。我接手过两个类似需求一个给美食小程序做后台菜谱库一个给企业食堂做菜品数据库共同点都是菜谱的核心价值不在文字排版而在结构化——用户搜“鸡胸肉”能查到所有用它做的菜后台能按分类筛、按耗时过滤、导每日菜单。这篇文章就按我落地的顺序把实体建模、MySQL 建表、数据导入、并发避坑和检索进阶一次讲完。适合打算用 MySQL 或 SQLite 自建菜谱库的开发者也适合做课程设计时想把菜谱库做规范的学生。2. 菜谱库的实体建模表怎么拆、字段怎么定、选哪个数据库建表之前先把实体画清楚画不清楚后面全是补丁。菜谱领域和电商商品有个关键差别一个菜对应多个食材而一个食材又出现在很多菜里这是典型的多对多关系。如果不拆关联表你今天把“番茄炒蛋”的食材塞进一个字段明天就发现没法按食材统计、没法算营养、没法做“缺什么食材能做什么菜”的反查。2.1 最少四张表菜谱、食材、关联、步骤拆到能查询为止我一般会把菜谱库拆成四个核心实体对应四张表。第一张是菜谱主表 recipe放菜名、别名、分类、难度、烹饪时长、封面图、描述。第二张是食材表 ingredient放食材名和可选的热量等属性。第三张是关联表 recipe_ingredient记录“哪个菜用了哪个食材、用了多少、什么单位”。第四张是步骤表 recipe_step按顺序存每一个操作步骤和大概耗时。为什么步骤要单独拆表而不是塞在 recipe 表里加一个 step_text 字段因为步骤是一对多拆开以后可以按 step_no 排序可以存每步耗时后台还能单独编辑某一步。如果塞成一个大文本想改第三步就得整段替换想统计每道菜的总操作耗时也得先解析文本。字段类型上difficulty 用 TINYINT0 代表简单、1 普通、2 困难不要直接存“简单/普通/困难”字符串否则排序和统计都麻烦。cooking_time_min 用 SMALLINT单位统一成分钟。关联表里的 quantity 用 DECIMAL(8,2)因为有的菜谱写 0.5 个鸡蛋有的写 2 勺酱油整数根本装不下。unit 用 VARCHAR(20) 存“个、克、毫升、勺”。这里有一个常见的反面设计把食材直接用逗号拼成一个字符串比如 ingredients 鸡蛋,番茄,盐。几百条数据时确实没感觉等数据量到几万条想查“哪些菜用到鸡蛋”就只能全表 LIKE索引完全失效。这也是数据库基础知识里最容易被忽略的一点能用关系表达的数据别用字符串硬存。2.2 三个存储选型MySQL、SQLite、向量库各管哪个环节菜谱库的存储选型我通常按这个标准判断主库用 MySQL离线或嵌入式场景用 SQLite语义检索再考虑向量数据库。三者不是互相替代而是管不同环节。存储适合场景优点局限MySQL服务端主库、后台管理、接口查询、事务写入事务可靠、并发能力够、主从和同步工具链成熟需要部署和维护单机有性能上限SQLite小程序离线包、App 内置菜谱、单机工具单文件、零配置、随包分发并发写弱不适合多端同时写向量数据库按食材语义反查菜谱、相似菜谱推荐能处理“手头食材能做什么菜”这类模糊查询不适合做业务主库要单独维护一套如果你的项目要上多端读写、要后台管理直接 MySQL别想着先用 SQLite 顶着以后再迁移。迁移本身不难但中途换库会打断业务节奏。反过来如果只是做一个本地单机工具用 SQLite 是最划算的Linux 下单文件数据库跑起来几乎不用维护。至于向量库菜谱量没到几万条以上不用急着上后面检索章节我会展开讲它解决什么问题。接触过 Oracle、达梦、人大金仓这类库的读者会发现表结构设计思路完全通用SQL 方言的差异很小。菜谱库的建模重点永远在“怎么拆表”而不是“用什么数据库”。3. 用 MySQL 建出食谱菜谱大全建表 SQL 与增删改查套路数据模型定好后落到 MySQL 就是一套标准的建库建表。这里我把四张表的完整 SQL 写出来每一处配置都有对应的理由直接抄没问题但要看清哪些是业务字段、哪些是防坑配置。3.1 建库建表主键、外键和 utf8mb4一次配到位CREATE DATABASE IF NOT EXISTS recipe_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE recipe_db; CREATE TABLE category ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, sort INT NOT NULL DEFAULT 0 ) ENGINEInnoDB; CREATE TABLE recipe ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, alias VARCHAR(255) NULL, category_id INT UNSIGNED NULL, difficulty TINYINT NOT NULL DEFAULT 1 COMMENT 0简单 1普通 2困难, cooking_time_min SMALLINT UNSIGNED NOT NULL DEFAULT 0, main_image VARCHAR(255) NULL, description TEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_recipe_category FOREIGN KEY (category_id) REFERENCES category(id), KEY idx_name (name), KEY idx_category (category_id) ) ENGINEInnoDB; CREATE TABLE ingredient ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, calory_per_100g DECIMAL(8,2) NULL ) ENGINEInnoDB; CREATE TABLE recipe_ingredient ( recipe_id INT UNSIGNED NOT NULL, ingredient_id INT UNSIGNED NOT NULL, quantity DECIMAL(8,2) NULL, unit VARCHAR(20) NULL, PRIMARY KEY (recipe_id, ingredient_id), CONSTRAINT fk_ri_recipe FOREIGN KEY (recipe_id) REFERENCES recipe(id) ON DELETE CASCADE, CONSTRAINT fk_ri_ingredient FOREIGN KEY (ingredient_id) REFERENCES ingredient(id) ) ENGINEInnoDB; CREATE TABLE recipe_step ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, recipe_id INT UNSIGNED NOT NULL, step_no TINYINT UNSIGNED NOT NULL, description TEXT NOT NULL, duration_sec INT UNSIGNED NULL DEFAULT 0, CONSTRAINT fk_step_recipe FOREIGN KEY (recipe_id) REFERENCES recipe(id) ON DELETE CASCADE ) ENGINEInnoDB;这个建表 SQL 里有几个关键点。字符集选 utf8mb4而不是 utf8因为菜谱文案里经常出现特殊符号和 emojiutf8mb4 才能完整存下。排序规则 utf8mb4_unicode_ci 在中文场景下兼容性更好按菜名排序时不会出现“拼音不该排一起却排到一起”的怪问题。所有表都用 InnoDB为了事务和外键。recipe_ingredient 和 recipe_step 里的外键都带了 ON DELETE CASCADE意思是删掉一个菜谱时它的食材关联和步骤自动清掉。这个设计能少写很多删除代码但代价是删除主表时如果数据量特别大级联删除可能让事务长时间持锁后面避坑章节会细说。索引方面recipe 表建了 idx_name 和 idx_category分别服务“按菜名查”和“按分类筛”。name 不加 UNIQUE因为同名菜谱很常见但 ingredient.name 必须 UNIQUE否则食材表会出现“鸡蛋”和“鸡蛋 ”两条记录关联表就乱了。3.2 增删改查新增一个菜谱要动几张表查询怎么写才不慢插入一个菜谱最少要动三张表recipe 主表、recipe_ingredient 关联表、recipe_step 步骤表。这三条 INSERT 必须包在同一个事务里否则会出现“菜谱主表有了食材关联没插进去”的半截数据。START TRANSACTION; INSERT INTO recipe (name, alias, category_id, difficulty, cooking_time_min, description) VALUES (番茄炒蛋, 西红柿炒鸡蛋, 2, 0, 10, 经典家常菜); SET rid LAST_INSERT_ID(); INSERT INTO recipe_ingredient (recipe_id, ingredient_id, quantity, unit) VALUES (rid, 12, 2.00, 个), (rid, 8, 3.00, 个); INSERT INTO recipe_step (recipe_id, step_no, description, duration_sec) VALUES (rid, 1, 鸡蛋打散番茄切块, 180), (rid, 2, 热油炒蛋至凝固盛出, 120), (rid, 3, 番茄炒出汁后回倒鸡蛋调味出锅, 240); COMMIT;这里用 SET rid LAST_INSERT_ID() 拿到刚插入的 recipe 自增主键关联表和步骤表都靠这个 ID 挂到主记录下。千万别自己算一个 ID 手写进去并发插入时两个事务拿同一个 ID主键冲突直接报错。按食材反查菜谱是菜谱库最核心的查询写成这样SELECT r.id, r.name FROM recipe r INNER JOIN recipe_ingredient ri ON ri.recipe_id r.id WHERE ri.ingredient_id IN (12, 8) GROUP BY r.id, r.name HAVING COUNT(DISTINCT ri.ingredient_id) 2;这个 SQL 要解释一下。IN (12, 8) 表示限定食材 ID 集合HAVING COUNT(DISTINCT ri.ingredient_id) 2 表示这个菜必须同时包含两种食材。如果没有 HAVING只写 IN那“番茄炒蛋”和“番茄蛋汤”都会被查出来但你输入的是“番茄”和“鸡蛋”希望得到的是同时用了这两种食材的菜。HAVING 的数量必须等于 IN 里的食材数量这是按食材反查的关键参数。更新和删除相对简单但删除顺序有讲究。更新菜谱基本信息直接 UPDATE recipe如果要改食材清单我习惯先删掉 recipe_ingredient 里这个菜的所有关联再重新插入保证幂等。删除一个菜谱时因为关联表和步骤表都配了 ON DELETE CASCADE理论上只删主表就行但如果你在代码里用了外键检查且事务隔离级别较高还是建议先手动清子表再删主表避免意外锁等待。DELETE FROM recipe_ingredient WHERE recipe_id 100; DELETE FROM recipe_step WHERE recipe_id 100; DELETE FROM recipe WHERE id 100;3.3 数据量上来后再改表ALTER TABLE 的三个注意点开发初期表结构没定死很正常菜谱库常见的改表操作是加字段、加唯一索引、改字段长度。但如果数据量已经上来ALTER TABLE 不是随便执行就完事的。加字段示例ALTER TABLE recipe ADD COLUMN season VARCHAR(20) NULL AFTER category_id;加唯一索引之前一定先查重复数据否则直接报错SELECT name, COUNT(*) FROM recipe GROUP BY name HAVING COUNT(*) 1;第三个注意点是执行时机。MySQL 5.6 之后支持在线 DDL但大表上的 ALTER TABLE 还是会带来行锁和主从延迟。我的习惯是线上表结构变更放到业务低峰期并且先在测试库上跑一遍同样数据量级的操作估算耗时。菜谱库如果只是几万条改表基本秒级完成如果上了百万条就要考虑用 gh-ost 这类工具做在线变更不能直接 ALTER 硬扛。4. 把菜谱数据灌进库JSON 清洗、批量导入与失败回滚建完表之后马上会遇到新问题菜谱数据从哪来、以什么格式来。常见来源是爬虫抓的网页、开放 API 返回的 JSON、或者别人给的 Excel/CSV。不管哪种数据几乎都不能直接入库因为原始数据里的字段命名、嵌套结构、单位写法五花八门。4.1 清洗脚本把嵌套 JSON 拍平成数据库字段我处理过最常见的原始 JSON 结构是这样一个菜谱对象里有 name、steps 数组、ingredients 数组而 ingredients 数组里每个元素又有 name、quantity、unit。这个结构其实和我们设计的四张表是对应的但 quantities 经常是“适量”“少许”这种非数字值单位也混着“克”、“g”、“勺”。写一个 Python 清洗脚本把嵌套结构转成两张子表的行数据import json ingredient_map {} # 食材名 - id 的映射 def load_ingredients(conn): cur conn.cursor() cur.execute(SELECT id, name FROM ingredient) for iid, name in cur.fetchall(): ingredient_map[name.strip()] iid def get_ingredient_id(conn, name): name name.strip() if name in ingredient_map: return ingredient_map[name] cur conn.cursor() cur.execute(INSERT INTO ingredient (name) VALUES (%s), (name,)) conn.commit() ingredient_map[name] cur.lastrowid return cur.lastrowid def parse_recipe(raw): steps [] for no, item in enumerate(raw.get(steps, []), 1): steps.append((no, item.get(text, ), int(item.get(sec, 0)))) ingredients [] for item in raw.get(ingredients, []): ingredients.append(( get_ingredient_id(item[name]), item.get(quantity), item.get(unit, ) )) return raw[name], raw.get(alias), steps, ingredients这段脚本的逻辑是先加载一次现有食材表建立“名字到 ID”的内存映射后面处理每道菜时食材如果已经存在就直接拿 ID不存在就先插入食材表再拿 ID。如果不做这个缓存每遇到一个食材都查一次库几万条菜谱导入会慢到怀疑人生。清洗时要注意一个细节原始数据里的 quantity 可能是字符串“适量”入库前要么转成 NULL要么统一换算成标准单位。我的做法是分成两层先做单位归一化把“g”统一成“克”、把“ml”统一成“毫升”再把“适量”这类无法量化的值存成 NULL查询时按 NULL 处理不参与数量计算。如果数据源是超大 JSON一次 json.load 读进内存会爆需要改用流式解析逐条处理。4.2 批量导入executemany 配事务导入失败有后悔药清洗完成后批量入库我最常用的方式是 Python 的 mysql-connector 加 executemany配合手动事务控制。from mysql.connector import connect conn connect( host127.0.0.1, userroot, passwordyour_password, databaserecipe_db, autocommitFalse ) with conn.cursor() as cur: cur.execute(SET FOREIGN_KEY_CHECKS0) for batch_start in range(0, len(recipes), 500): batch recipes[batch_start:batch_start 500] try: for raw in batch: name, alias, steps, ingredients parse_recipe(raw) cur.execute( INSERT INTO recipe (name, alias, difficulty, cooking_time_min) VALUES (%s, %s, %s, %s), (name, alias, raw.get(difficulty, 1), raw.get(cooking_time_min, 0)) ) rid cur.lastrowid cur.executemany( INSERT INTO recipe_step (recipe_id, step_no, description, duration_sec) VALUES (%s, %s, %s, %s), [(rid, no, desc, sec) for no, desc, sec in steps] ) cur.executemany( INSERT INTO recipe_ingredient (recipe_id, ingredient_id, quantity, unit) VALUES (%s, %s, %s, %s), [(rid, iid, qty, unit) for iid, qty, unit in ingredients] ) conn.commit() except Exception as e: conn.rollback() print(fbatch starting at {batch_start} failed: {e}) cur.execute(SET FOREIGN_KEY_CHECKS1)每 500 条菜谱提交一次事务这个参数是折中结果。一次事务太大undo 日志膨胀失败回滚要等很久一次事务太小比如每条一提交插入 10 万条菜谱要产生 10 万次提交性能完全扛不住。500 是我在菜谱数据量级下试过比较稳的值如果你是几千条小数据直接一个事务全包也行。SET FOREIGN_KEY_CHECKS0 是导入加速的关键关闭外键检查后先插子表再插主表都不会报错插入顺序彻底自由。但代价是脏数据不会被外键拦住比如 ingredient_id 指向一个不存在的食材导入期间不会暴露所以这个开关只建议在一次性导入时用导入完成后必须恢复为 1。如果数据源不是 JSON 而是 CSV可以用 LOAD DATA INFILE它比程序逐条插入快一个数量级但要求 CSV 的列顺序和表结构完全一致而且对字符集敏感文件里混着中文和特殊符号时容易出问题。我通常只在一次性导 CSV 时用它日常增量更新还是走程序入库更可控。5. 多端读写后的并发与同步锁、死锁和同步延迟的避坑现场菜谱库跑起来以后大概率是后台编辑、小程序查询、爬虫增量写入同时发生。这个阶段遇到的问题就不再是表结构了而是并发控制、死锁和主从同步这些数据库本身的机制问题。5.1 事务隔离级别与锁菜谱库并发写入为什么会卡死MySQL InnoDB 默认隔离级别是 Repeatable Read这个级别下两个事务同时往 recipe_ingredient 表插数据时可能因为间隙锁互相等待最终触发死锁。我实际遇到过的场景是事务 A 先插 recipe_id1 的关联再插 recipe_id2 的关联事务 B 正好按相反顺序插。两个事务都持有了对方下一步要锁的间隙InnoDB 只能牺牲其中一个抛出一个死锁错误。查看死锁信息的命令是SHOW ENGINE INNODB STATUS;重点看输出里的 LATEST DETECTED DEADLOCK 段落里面会明确列出两个事务各持有什么锁、在等什么锁。解决思路有两个一是让所有事务按同一个顺序操作子表比如一律按 recipe_id 升序插入破坏循环等待二是把隔离级别降到 READ COMMITTED减少间隙锁的使用范围。MySQL 连接池里统一设置隔离级别的语句是SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;但要注意这个设置如果只在某个会话里执行连接池里的其他连接不会生效。正确做法是在连接池初始化配置里统一指定或者直接在 JDBC 连接串里加 transactionIsolationREAD_COMMITTED这样每个新连接都带上这个参数。5.2 数据库同步工具与主从延迟、位点和双写顺序菜谱库的读请求通常远多于写请求等主库查询压力上来后第一件该做的事就是做主从读写分离。主库负责写入和事务操作从库扛查询。MySQL 自带的 binlog 同步是基础方案配置简单但只能从主库同步到 MySQL 从库。如果要把数据同步到 Elasticsearch、向量库或者另一个异构存储就需要用 canal 这类工具解析 binlog再把变更推给下游。DataX 则适合做离线批量同步比如每天把菜谱库全量同步到数仓做统计分析。这里有一个容易踩的坑主从同步有延迟延迟期间从库查不到刚写入的数据。比如后台刚更新了菜谱用户在小程序里刷新从库还没收到 binlog就看到旧数据。解决方式是按业务分级实时性要求高的接口比如“编辑后立即查看”强制走主库普通列表查询走从库。同时监控 Seconds_Behind_Master 这个指标它表示从库落后主库的秒数长时间大于 0 就要检查是不是有大事务阻塞了同步。如果菜谱数据还要写进缓存或搜索服务就涉及到双写顺序问题。我的习惯是先写数据库再更新缓存或搜索索引因为数据库是事实来源。缓存更新失败就用同步消息补偿让下游任务重新拉取。反过来先写缓存再写库一旦数据库写入失败缓存里就是一条不存在的菜谱数据对不上。5.3 避坑记录五个真实翻车案例现象、原因、解决办法坑 1插入关联数据时被外键拦住。现象执行 INSERT INTO recipe_ingredient 时直接报 Cannot add or update a child row外键关联找不到主表记录。原因菜谱主表的 INSERT 还没提交或者代码里换了插入顺序关联表先于主表执行。解决严格遵循先插 recipe 主表用 LAST_INSERT_ID() 拿自增主键再插关联表和步骤表的顺序且全部包在同一个事务里。坑 2菜名模糊查询越来越慢。现象数据量到两万条后SELECT ... WHERE name LIKE %鸡蛋% 要扫一到两秒。原因前缀带 % 的 LIKE 无法走普通 B-Tree 索引只能全表扫描。解决改用全文索引或者把菜名相关的搜索请求挪到 ElasticsearchMySQL 只承担结构化查询。坑 3删除菜谱主表卡死。现象DELETE FROM recipe WHERE id 100 执行后长时间无响应进程一直处于 Sleep 状态。原因关联表和步骤表数据量不小级联删除持有大量行锁同时有其他事务正在读写同一批数据形成锁等待。解决先手动清空 recipe_ingredient 和 recipe_step 里对应 recipe_id 的数据最后删主表把一个大事务拆成多个小事务。坑 4导入 10 万条菜谱一个事务全包回滚等了十分钟。现象导入脚本跑一半报错执行 rollback 后数据库卡了很久才恢复。原因单事务太大undo 日志膨胀回滚要重放所有反向操作。解决按 500 条一批提交每批失败只回滚当前批次不影响已提交的数据回滚耗时也能控制在秒级。坑 5导入脚本和后台查询同时跑应用报连接超时。现象MySQL 连接数打满后台接口频繁报 Too many connections 或连接等待超时。原因连接池默认 8 个连接全被导入脚本的长事务占住查询请求拿不到连接。解决导入脚本用独立的数据库连接不经过业务连接池连接池最大连接数按并发量和数据库 max_connections 合理上调同时限制导入脚本的并发数。6. 从“能查”到“好搜”全文索引、向量检索和数据质量验证到了这一章菜谱库已经跑起来了但“按食材精确匹配”和“用户真正想搜出个菜”之间还有距离。菜谱搜索有两个典型场景一是用户只记得菜名里的几个字二是用户手里有食材但不知道做什么前者靠全文索引后者靠向量检索。6.1 中文菜谱的全文索引一个 ngram 分词参数让搜索变准MySQL 5.7 之后内置了 ngram 全文解析器可以直接给中文文本建全文索引不用额外装分词插件。ALTER TABLE recipe ADD FULLTEXT INDEX ft_recipe_name (name) WITH PARSER ngram; SELECT id, name FROM recipe WHERE MATCH(name) AGAINST(番茄炒蛋 IN NATURAL LANGUAGE MODE);ngram 默认把文本按两个字符切词“番茄炒蛋”会被切成“番茄”“茄炒”“炒蛋”三个 token匹配时按相关性排序比 LIKE %番茄% 强在两点有索引、有排序。如果觉得默认切词粒度不合适可以在 MySQL 配置里调 ngram_token_size改成 1 或 3但要重启实例并重建全文索引代价不小。菜谱名偏短我认为默认的 2 最稳。6.2 把菜谱变成向量按“手头食材”反查能做什么菜全文索引解决不了“我有鸡胸肉和彩椒能做什么菜”这种开放问题。这类搜索更适合把菜谱的食材列表拼成一句文本用向量模型转成 embedding再存到向量数据库里。查询时把“鸡胸肉 彩椒”也转成向量做 top_k 余弦相似度检索。from sentence_transformers import SentenceTransformer model SentenceTransformer(moka-ai/m3e-base) query_vec model.encode(鸡胸肉 彩椒) # 用 query_vec 到向量数据库做 top_k 近似检索这个方案的好处是容忍表达差异用户说“鸡胸肉”还是“鸡大胸”都能命中相近语义。但要注意向量检索返回的是相似度排序不是精确匹配作为主查询的补充入口更合适。菜谱量没到几万条以上先用 MySQL 的 SQL 反查完全够用别急着上向量库增加运维负担。6.3 一套菜的检索回归测试用 50 个查询验证搜索结果给菜谱库做搜索功能后我习惯用一组固定查询做回归。挑 50 个典型搜索词覆盖菜名精确匹配、模糊词、方言别名、食材组合每个查询记录期望命中的菜谱 ID。代码上线或数据更新后跑一遍对比能快速发现搜索召回变了没有。数据验证也可以借事务回滚来做到零残留START TRANSACTION; INSERT INTO recipe (name, difficulty, cooking_time_min) VALUES (测试菜谱, 0, 10); SET rid LAST_INSERT_ID(); INSERT INTO recipe_ingredient (recipe_id, ingredient_id, quantity, unit) VALUES (rid, 12, 1.00, 个); SELECT * FROM recipe WHERE name 测试菜谱; ROLLBACK;这段 SQL 插入测试数据、验证查询结果、再全部回滚线上库不会留任何测试痕迹。我现在的习惯是接到菜谱类项目先花半天把数据模型画清楚再谈接口。模型对了后面加索引、加字段都是小事模型错了每写一条查询都在还债。希望帮到你。本文还有配套的精品资源点击获取
返回列表