资讯动态

万年历数据入MySQL:从JSON导入到表设计与校验的完整指南

发布时间:2026/10/13 17:38:47 来源:尧图企业网站定制
简介一套针对 MySQL 的完整万年历数据库脚本时间横跨1970年至2100年全面涵盖农历、二十四节气、财神方位、宜忌、星座、天干地支与五行等传统日历信息适合需要快速搭建日期查询功能的开发者也适合学习数据库设计与导入实践的初学者。压缩包共2个文件分别为1个CSV数据文件和1个SQL脚本文件整体大小约1.86MB。SQL脚本负责创建多张表结构并定义字段类型与约束CSV数据文件可通过LOAD DATA INFILE批量导入两者配合即可生成一套可直接使用的日历数据库。资源已吸引1609人学习浏览兼具资料完整性与实战参考价值。拿到后可直接执行脚本建库导数据再按年份、日期、节气、方位、星座等维度编写查询与应用方便用于个人项目、工具网站或数据库课程练习。1. 万年历数据入 MySQL“最全”不是导入一次就完事把“万年历脚本 mysql 数据库黄历”这几个词拼在一起背后其实是一个很典型的落地需求你要的不只是某一天的宜忌而是把一整段历史区间内的公历、农历、干支、五行、冲煞、吉神凶煞全部结构化存进 MySQL提供给业务系统查询。这个场景在传统行业特别常见——做内部管理系统、OA 审批、CRM 订单交付经常要附带一个“今天适不适合开工/签约/出行”的提示。市面上的第三方 API 要么收费、要么限流、要么返回字段不齐数据不在自己库里出问题都找不到人赔。“最全”这两个字才是真正的坑。数据源杂、格式乱、宜忌文本有歧义导入脚本跑完不等于数据正确等上线后领导问你“2 月 9 日到底宜不宜动土”而你答不上来的时候才知道前面偷的懒全要还。这篇笔记就是把你从数据源选型一路带到表设计、Shell/Python 双脚本导入、SQL 校验和常见报错排查的完整路径适合准备自己动手做内网黄历查询功能的人。新手能照着一步步跑通熟手可以直接拿走核对清单和避坑经验。2. 数据源与表设计别急着写脚本先定字段边界2.1 数据源怎么选结构化 JSON 优先于网页抓取做万年历脚本的第一件事不是写代码而是决定数据从哪来。我自己踩过几次网页抓取的坑目标网站改个 class 名、加个验证码、或者把农历接口从 HTTP 切成 HTTPS脚本就废了。更麻烦的是网页抓下来的数据是“给人看”的排版公历日期、农历日期混在一段文本里解析时错一位就是全表崩。所以首选是找结构化程度高的数据源。常见做法是找一个覆盖区间足够大的 JSON 数据包每条记录包含公历日期、农历月日、干支、五行、冲煞、宜忌列表这些字段格式尽量统一。判断数据源好不好用我一般看三点一是区间覆盖能不能覆盖 1900-2100 这种常见范围二是字段是否拆分到“机器可读”的程度比如宜忌最好是数组而不是一段用顿号分隔的字符串三是授权是否清晰注明来源和允许使用的范围免得后面业务上线了惹麻烦。拿到 JSON 之后再考虑要不要二次加工。比如原始数据里“宜”字段是“嫁娶.祭祀.祈福”这种点号分隔的字符串最省事的办法是在导入脚本里按分隔符拆成数组再入库而不是每次查询时再拆后面写 SQL 会轻松很多。2.2 建表日期主键、宜忌用 JSON 还是拆列表结构是万年历脚本里最值得花时间的部分。字段改一次所有导入脚本和查询 SQL 全要跟着改所以先想清楚再建表。我的建议是主表用公历日期做 PRIMARY KEY因为公历日期是唯一的、连续的天然适合做唯一标识农历日期在同一天可能对应不同的公历日期闰月不适合做主键。CREATE TABLE calendar_info ( solar_date DATE PRIMARY KEY COMMENT 公历日期, lunar_date VARCHAR(20) NOT NULL COMMENT 农历日期如2024年腊月廿三, lunar_year VARCHAR(10) COMMENT 农历年如癸卯, lunar_month VARCHAR(10) COMMENT 农历月如腊月, lunar_day VARCHAR(10) COMMENT 农历日如廿三, ganzhi_year VARCHAR(10) COMMENT 年柱干支如癸卯, ganzhi_month VARCHAR(10) COMMENT 月柱干支, ganzhi_day VARCHAR(10) COMMENT 日柱干支, wuxing_day VARCHAR(20) COMMENT 日五行如金/木/水/火/土, chong VARCHAR(30) COMMENT 冲煞如冲虎(壬寅)煞南, sha VARCHAR(5) COMMENT 煞方如南, yi JSON COMMENT 宜JSON数组如[嫁娶,祭祀], ji JSON COMMENT 忌JSON数组, pengzu VARCHAR(100) COMMENT 彭祖百忌如庚不經絡, jishen VARCHAR(100) COMMENT 吉神宜趋, xiongshen VARCHAR(100) COMMENT 凶煞宜忌, jieqi VARCHAR(20) COMMENT 节气如立春/雨水, is_holiday TINYINT DEFAULT 0 COMMENT 是否法定节假日0否1是, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_solar_date (solar_date), KEY idx_lunar_date (lunar_date(10)), KEY idx_jieqi (jieqi) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT万年历黄历主表;几个字段设计的细节说一下。lunar_date保留完整字符串用于直接展示同时拆出lunar_month、lunar_day方便按“腊月廿三”这种条件做查询如果你只存完整字符串以后想筛“所有初五”的时候就要写 LIKE 或者 SUBSTRING_INDEX查询性能和使用体验都不好。ganzhi_year和ganzhi_month虽然可以从lunar_date反推但反推逻辑涉及农历编码表在 SQL 里写会很痛苦入库时直接冗余存一份查询时免计算。yi和ji用了 JSON 类型。这里有一个取舍如果拆成单独的calendar_yi、calendar_ji关联表确实更符合第三范式但万年历查询是典型“一次查一行、整行展示”的场景拆表之后每次查询都要做两次 JOIN收益很小。用 JSON 的好处是一行存完Python 读出来直接就是 list前端拿到就能渲染MySQL 从 5.7 开始支持 JSON 类型查询时也可以用JSON_CONTAINS做匹配功能上不差。is_holiday默认值设为 0这是为了配合后面脚本导入时只更新有值的字段。如果默认值允许 NULL业务查询里WHERE is_holiday 0就永远查不到新导入的行这种小细节很容易让人排查半天。2.3 边界字段节气、时辰、彭祖百忌要不要单独建表主表之外还有两类字段要想清楚节气和时辰。节气是每个公历日期最多一个属于“一日期一值”直接放进主表的jieqi字段就够但如果团队里做农事系统、排产系统需要精确到节气交接时刻比如“2024 年立春时间是 2 月 4 日 16:26”那就要单独建一张节气表存节气的精确时间、公历日期、农历对应关系。时辰表和节气表不一样它和日期是一对多的关系一天有 12 个时辰每个时辰有各自的吉凶宜忌。如果业务要展示“今日每个时辰宜什么、忌什么”我建议单独建表不要往主表里塞 12 个字段CREATE TABLE calendar_shichen ( id INT AUTO_INCREMENT PRIMARY KEY, solar_date DATE NOT NULL, shichen VARCHAR(10) NOT NULL COMMENT 时辰如子时/丑时, time_range VARCHAR(20) COMMENT 时间段如23:00-00:59, yi VARCHAR(100) COMMENT 此时辰宜, ji VARCHAR(100) COMMENT 此时辰忌, wuxing VARCHAR(20) COMMENT 此时辰五行, UNIQUE KEY uk_date_shichen (solar_date, shichen), CONSTRAINT fk_shichen_date FOREIGN KEY (solar_date) REFERENCES calendar_info(solar_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT时辰宜忌表;这里注意如果不是刚需不要一上来就建时辰表。我见过一个项目把时辰表建好了结果数据源里根本没有对应的时辰宜忌数据表空置了半年。字段边界的原则是“数据源有什么业务要什么再建什么”千万不要先建表再找数据否则导入脚本写起来会非常痛苦。3. 全量导入脚本Shell 与 Python 两条路怎么走3.1 环境准备MySQL 8.0 建库授权与连接参数脚本写得再好连不上数据库也白搭。这里先解决环境问题顺便说一句很多人在 mysql 安装配置教程里卡住的点大多不是安装本身而是安装之后不知道要给脚本账号单独授权。我习惯的做法是建一个专用账号而不是用 root 跑业务脚本免得哪天脚本里一个 DELETE 忘写 WHERE 把整个库清空。CREATE DATABASE calendar_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER calendar_applocalhost IDENTIFIED BY your_strong_password; GRANT SELECT, INSERT, UPDATE, DELETE ON calendar_db.* TO calendar_applocalhost; FLUSH PRIVILEGES;三个要点说清楚。第一建库时直接指定 utf8mb4这是 MySQL 8.0 的默认字符集也是中文数据不出现乱码的前提。第二授权粒度控制在单库calendar_db.*不是*.*这样即使脚本被注入影响范围也被限制住。第三FLUSH PRIVILEGES在通过 GRANT 语句授权时其实不是必须的但如果你手动改过mysql.user表那这句就是必须的写上没坏处养成习惯。连接参数方面脚本里请统一使用127.0.0.1而不是localhost。这两个的区别在 MySQL 连接时很微妙用localhost会走 Unix socket用127.0.0.1会走 TCP。后面排查 ERROR 2002 时这个区别往往是罪魁祸首这里提前埋个伏笔。3.2 Shell 脚本方案for 循环 mysql 命令行适合一次性全量如果你手里的 JSON 数据是按年份拆成多个文件的Shell 脚本是最直白的方案。用for循环遍历目录下的所有 JSON 文件逐个调用mysql命令行导入脚本短、依赖少、部署简单。下面这个脚本是我实际用过的简化版#!/bin/bash # load_calendar.sh - 批量导入万年历 JSON 数据到 MySQL MYSQL_CMDmysql -ucalendar_app -pyour_strong_password -h127.0.0.1 --default-character-setutf8mb4 calendar_db INPUT_DIR./calendar_data for f in $INPUT_DIR/*.json; do echo processing: $f $MYSQL_CMD -e SET json : LOAD_FILE($(realpath $f)); INSERT INTO calendar_info (solar_date, lunar_date, lunar_year, ganzhi_day, wuxing_day, chong, sha, yi, ji, pengzu, jieqi) SELECT JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.solar_date)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.lunar_date)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.lunar_year)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.ganzhi_day)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.wuxing_day)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.chong)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.sha)), JSON_EXTRACT(j.doc, $.yi), JSON_EXTRACT(j.doc, $.ji), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.pengzu)), JSON_UNQUOTE(JSON_EXTRACT(j.doc, $.jieqi)) FROM (SELECT json AS doc) j; if [ $? -eq 0 ]; then echo OK: $f else echo FAIL: $f exit 1 fi done这个脚本的核心逻辑是先把 JSON 文件用LOAD_FILE()读成字符串再用JSON_EXTRACT()从 JSON 里取出每个字段最后 INSERT 到表中。json是 MySQL 会话变量j.doc是它的别名引用这种写法避免了把 JSON 内容直接拼进 SQL 字符串里产生的转义地狱。参数说明realpath是为了把相对路径转成绝对路径因为LOAD_FILE()对路径很敏感如果 MySQL 的secure_file_priv限制了可读目录这里会直接 FAIL--default-character-setutf8mb4必须加否则中文内容在命令行传输时可能被转成 latin1$?检查上一条命令的返回值非 0 就退出这个习惯能让你第一时间发现导入失败的文件而不是跑完整个目录才发现数据是空的。这个方案的局限也很明显Shell 里做数据清洗很笨拙比如 JSON 里如果有某个字段缺失JSON_EXTRACT返回 NULL 值你得在 SQL 里写一堆IFNULL兜底。所以 Shell 方案适合“数据已经非常规整、只是需要灌进去”的全量导入场景。3.3 Python 脚本方案pymysql executemany 批量写入数据源字段不干净时我一般会改用 Python。Python 的优势在于读 JSON 用原生json.load()清洗逻辑写在 Python 里比写在 SQL 里直观得多写库用executemany()批量执行性能比逐条 INSERT 快一个数量级还能顺手打印导入进度和错误日志。#!/usr/bin/env python3 # load_calendar.py - 从 JSON 导入万年历数据支持重复执行 import json import glob import logging import pymysql logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) DB_CONFIG { host: 127.0.0.1, port: 3306, user: calendar_app, password: your_strong_password, database: calendar_db, charset: utf8mb4, autocommit: False, } INSERT_SQL INSERT INTO calendar_info (solar_date, lunar_date, lunar_year, lunar_month, lunar_day, ganzhi_year, ganzhi_month, ganzhi_day, wuxing_day, chong, sha, yi, ji, pengzu, jieqi, is_holiday) VALUES (%(solar_date)s, %(lunar_date)s, %(lunar_year)s, %(lunar_month)s, %(lunar_day)s, %(ganzhi_year)s, %(ganzhi_month)s, %(ganzhi_day)s, %(wuxing_day)s, %(chong)s, %(sha)s, %(yi)s, %(ji)s, %(pengzu)s, %(jieqi)s, %(is_holiday)s) ON DUPLICATE KEY UPDATE lunar_dateVALUES(lunar_date), lunar_yearVALUES(lunar_year), ganzhi_dayVALUES(ganzhi_day), wuxing_dayVALUES(wuxing_day), yiVALUES(yi), jiVALUES(ji), pengzuVALUES(pengzu), jieqiVALUES(jieqi) def clean_item(raw): 清洗单条 JSON 记录兜底缺字段的情况 return { solar_date: raw.get(solar_date) or 1970-01-01, lunar_date: raw.get(lunar_date) or , lunar_year: raw.get(lunar_year) or , lunar_month: raw.get(lunar_month) or , lunar_day: raw.get(lunar_day) or , ganzhi_year: raw.get(ganzhi_year) or , ganzhi_month: raw.get(ganzhi_month) or , ganzhi_day: raw.get(ganzhi_day) or , wuxing_day: raw.get(wuxing_day) or , chong: raw.get(chong) or , sha: raw.get(sha) or , yi: json.dumps(raw.get(yi, []), ensure_asciiFalse), ji: json.dumps(raw.get(ji, []), ensure_asciiFalse), pengzu: raw.get(pengzu) or , jieqi: raw.get(jieqi) or , is_holiday: 1 if raw.get(is_holiday) else 0, } def main(): conn pymysql.connect(**DB_CONFIG) files sorted(glob.glob(./calendar_data/*.json)) total_rows 0 try: with conn.cursor() as cursor: for fp in files: with open(fp, encodingutf-8) as f: raw_list json.load(f) rows [clean_item(item) for item in raw_list] cursor.executemany(INSERT_SQL, rows) conn.commit() total_rows len(rows) logging.info(imported %d rows from %s, len(rows), fp) logging.info(done, total %d rows, total_rows) except Exception as e: conn.rollback() logging.error(import failed, rollback: %s, e) raise finally: conn.close() if __name__ __main__: main()代码逻辑拆开说。clean_item()是清洗层raw.get(yi, [])保证缺字段时给一个空数组json.dumps(..., ensure_asciiFalse)把 list 转成 JSON 字符串再存进 MySQL 的 JSON 字段这样存进去的内容是中文而不是\uXXXX转义序列。INSERT_SQL里的ON DUPLICATE KEY UPDATE是幂等设计同一份数据跑两遍第二条会变成更新而不是新增这个设计避免了重复导入时主键冲突直接报错。事务方面autocommitFalse下每导入一个文件提交一次而不是每行都提交也不等全部文件跑完再提交。每文件提交的粒度是性能和安全的折中一个文件几万条数据事务太大容易撑爆 undo log按文件粒度提交失败时最多回滚一个文件不影响已导入的其他年份。这里有一个参数选择值得说executemany()的第二个参数传的是 dict 列表对应 SQL 里的%(字段名)s占位符。如果你改成INSERT INTO ... VALUES (%s, %s)那种位置占位符rows就必须是 tuple 列表我用 dict 是为了让 SQL 可读性更好字段多的时候不容易错位。4. 查询与数据校验三个核对 SQL 让导入不翻车4.1 常用查询按公历查黄历、按月列出宜忌数据进库之后第一步永远是用最基础的查询验证“能不能查到”。先跑一个最简单的按日期查询确认字段映射没搞反-- 查指定日期的完整黄历信息 SELECT solar_date, lunar_date, ganzhi_year, ganzhi_month, ganzhi_day, wuxing_day, chong, sha, JSON_UNQUOTE(JSON_EXTRACT(yi, $[0])) AS yi_first, JSON_UNQUOTE(JSON_EXTRACT(ji, $[0])) AS ji_first, pengzu, jieqi FROM calendar_info WHERE solar_date 2024-02-10;注意yi字段是 JSON 类型直接用SELECT yi查出来的还是一段 JSON 文本。业务上通常需要取第一条宜、第一条忌所以这里用JSON_EXTRACT(yi, $[0])取数组第一个元素再包一层JSON_UNQUOTE把引号去掉。不要小看这个细节前端拿到带引号的字符串再渲染很容易出现页面显示嫁娶带双引号的翻车现场。按月查询比如查 2024 年 2 月哪天宜嫁娶可以这样写SELECT solar_date, lunar_date, yi FROM calendar_info WHERE solar_date BETWEEN 2024-02-01 AND 2024-02-29 AND JSON_CONTAINS(yi, 嫁娶) ORDER BY solar_date;JSON_CONTAINS(yi, 嫁娶)是 JSON 数组的包含查询注意第二个参数必须带双引号因为 JSON 数组里的元素是字符串。这里如果写成JSON_CONTAINS(yi, 嫁娶)会报错因为这不是合法的 JSON 字符串。ORDER BY solar_date让结果按公历日期排序这是查询里最常用的排序方式如果你需要按农历日期排序那就要靠lunar_date字段了但字符串排序结果并不符合农历语义所以业务上一般还是按公历日期排序展示。4.2 三个核对 SQL总量、空值率、重复农历日期查询能跑通不等于数据正确。导入完成之后我每次都会跑下面三个核对 SQL这三条是验收的基本门槛也是以后数据出问题时的第一排查手段。-- 核对 1总量是否覆盖预期区间1900-2100 约 73000 天 SELECT MIN(solar_date) AS min_date, MAX(solar_date) AS max_date, COUNT(*) AS total_rows, COUNT(DISTINCT solar_date) AS distinct_dates FROM calendar_info; -- 核对 2关键字段的空值率超过 1% 就要查数据源 SELECT COUNT(*) AS total_rows, SUM(CASE WHEN lunar_date IS NULL OR lunar_date THEN 1 ELSE 0 END) AS null_lunar_date, SUM(CASE WHEN ganzhi_day IS NULL OR ganzhi_day THEN 1 ELSE 0 END) AS null_ganzhi_day, SUM(CASE WHEN yi IS NULL OR yi [] THEN 1 ELSE 0 END) AS null_yi, SUM(CASE WHEN ji IS NULL OR ji [] THEN 1 ELSE 0 END) AS null_ji FROM calendar_info; -- 核对 3农历日期重复检查闰月场景下可能合法但大量重复要警惕 SELECT lunar_date, COUNT(*) AS cnt FROM calendar_info GROUP BY lunar_date HAVING cnt 1 ORDER BY cnt DESC LIMIT 10;核对 1 看的是数据量。1900-2100 年是 201 个公历年按 365 或 366 天算总量在 73400 行左右。如果MIN/MAX覆盖到了但total_rows明显偏少说明数据源中间有断层常见原因是某几年的 JSON 文件没下载全或者脚本中途失败没被发现。核对 2 看的是空值率。lunar_date为空说明导入映射写错了字段名yi为空数组说明数据源里本来就没有宜的数据也可能是清洗逻辑把字段丢了。这里我一般给自己定的标准是关键字段空值率不超过 1%超过 1% 就要回到数据源逐条检查。核对 3 看重复的农历日期。大部分年份农历日期不会重复只有闰月年才会出现两个“四月十五”之类的重复。如果非闰月年出现大量重复说明 JSON 数据本身有脏数据。这里不要直接删先用HAVING cnt 1结合solar_date去查具体是哪几天人工确认是否闰月导致的再决定怎么处理。4.3 用随机抽检 外部对照打破“自说自话”核对 SQL 全部通过也可能数据整体是错的——比如数据源把“宜”和“忌”整列搞反了。这种结构性错误是 SQL 检查不出来的所以我还会做一个抽检随机取 10 个日期把库里查出来的农历日期、干支、宜忌和公开的权威日历逐条人工比对。这个工作看起来土但它是整个验证流程里最重要的一道关。抽检不一定要全人工。可以找一个已经验证过的、你本地已有的日历数据文件做程序化比对但如果两个来源本来是同一份数据这个比对就没有意义了。所以关键是找一个“信得过的独立来源”比如印刷版老黄历或者多个不同来源的数据交叉验证。这个思路要贯穿数据全流程别等到上线了才想起来验证黄历数据错了用户一眼就能看出来。5. 万年历入 MySQL 的避坑清单从 2002 报错到重复导入翻车5.1 ERROR 2002 连不上socket 与 TCP 两种姿势别混用现象脚本跑起来报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因命令行里写的是mysql -ucalendar_app -p没带-h此时 MySQL 客户端默认走 Unix socket 连接而/tmp/mysql.sock文件不存在——可能是 MySQL 服务没启动也可能是 socket 路径配置不在默认位置。我之前排查一个服务器MySQL 的 socket 被配置到了/var/run/mysqld/mysqld.sock客户端却去/tmp找必然连不上。解决命令行加上-h127.0.0.1强制走 TCP或者指定 socket 路径mysql -ucalendar_app -p -S /var/run/mysqld/mysqld.sock。调试阶段推荐先用 MySQL Workbench 可视化客户端测试连接Workbench 的连接配置里能看到具体是走 TCP 还是 socket能省很多排查时间。Workbench 能连上而脚本连不上那问题基本就出在连接参数上。5.2 中文变问号utf8mb4 三层一致才能救现象导入脚本跑完SELECT lunar_date FROM calendar_info查出的是“????”或者一串乱码。原因字符集不一致。这条路上一共有四层数据库建库字符集、表字符集、连接字符集、以及客户端文件读取编码。哪一层不是 utf8mb4中文就可能在某一次转换里变成问号。最常见的情况是你建库时用了默认的 latin1然后 Python 连接时指定charsetutf8mb4两边掐架。解决建库时显式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciPython 连接参数里带上charsetutf8mb4读取 JSON 文件时用open(fp, encodingutf-8)三层一致后基本能解决。还有一个坑是数据源本身可能不是 UTF-8 编码比如某些数据包是 GBK 编码这种情况下json.load()会直接报编码错误需要先用iconv或 Python 的codecs模块转码再导入。5.3 重复导入数据翻倍唯一索引和幂等写入是后悔药现象第一次全量导入成功跑脚本做增量更新时发现COUNT(*)从 7 万变成了 14 万主键还是同一个日期数据却重复了。原因表结构没问题的话导入脚本没有处理“已存在的行”。Shell 方案里我用的是裸INSERT INTO没有ON DUPLICATE KEY UPDATE脚本第二次执行时主键冲突MySQL 默认报错但如果表没建唯一索引就会直接插入重复行。解决两条路都得上。建表时一定要有PRIMARY KEY (solar_date)让数据库从结构上拒绝重复写入 SQL 里加ON DUPLICATE KEY UPDATE让脚本天然支持重复执行。我刚才给的两个方案Shell 的简化版没做幂等Python 版做了实际使用时 Shell 版也要加上。这个教训我栽过一次事前觉得“跑一次就不跑了”结果数据源更新了脚本又跑了一遍全表数据翻倍最后只能全量清掉重导。5.4 存储过程黑匣子没有日志失败了你都不知道现象调用一个存储过程导入数据MySQL 返回成功但表中数据行数没变。原因存储过程内部可能因为某个字段类型不一致抛了异常但异常被DECLARE CONTINUE HANDLER吞掉了或者存储过程里的事务没有正确 COMMIT。这种问题的麻烦在于——存储过程本身像一个黑匣子你看不到中间步骤的输出。解决开发阶段不要在存储过程里吞异常。MySQL 存储过程默认遇到错误会回滚整个事务你把它包在事务里出错时直接看错误信息生产环境如果一定要用存储过程导入成功与否不要只看“调用完成”要拿SELECT COUNT(*)前后对比验证。我现在的习惯是任何导入任务跑完都打一条日志包含文件名、导入行数、耗时没有日志的脚本一律不认。5.5 数据源的日期边界1900 年之前和 2100 年之后怎么办现象导入脚本跑完发现 1900-01-01 之前的日期也进了表而且农历字段全为空。原因很多万年历数据源只保证 1900-2100 区间的准确性区间之外的数据可能是推算出来的农历和干支不一定可靠。如果你用的是“号称支持任意年份”的数据包一定要检查极端日期的字段是否真的完整。解决在清洗层加一个白名单判断if not (datetime(1900, 1, 1) raw_date datetime(2100, 12, 31)): continue。如果你确实需要更早的数据比如做清宫表、八字排盘这种业务那就要换更专业的历法数据源并且单独做一轮人工校验别拿一个通用 JSON 包凑合。6. 从“能查”到“能用”存储过程、增量修数与索引调优6.1 用存储过程封装“查某天黄历”业务查询语句写得太长前端每次调用都要拼 SQL容易出错。我可以把最常用的查询封装成存储过程入参是公历日期出参是完整的黄历信息。注意存储过程里要处理日期格式非法的情况避免传一个2024-13-45进来把数据库搞崩溃。DELIMITER $$ CREATE PROCEDURE sp_get_calendar_by_date( IN p_solar_date DATE ) BEGIN IF p_solar_date IS NULL THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT solar_date cannot be null; END IF; SELECT solar_date, lunar_date, ganzhi_year, ganzhi_month, ganzhi_day, wuxing_day, chong, sha, pengzu, jieqi, CASE WHEN is_holiday 1 THEN 节假日 ELSE 工作日 END AS holiday_flag, yi, ji FROM calendar_info WHERE solar_date p_solar_date; END$$ DELIMITER ;调用方式CALL sp_get_calendar_by_date(2024-02-10);。封装之后业务层不需要关心表结构也不用拼字符串应用代码里简化为一行调用同时存储过程可以加上权限控制只给查询账号EXECUTE权限避免业务方绕过封装直接操作表。6.2 增量修正过年期间宜忌调整用 UPDATE 而不是重导术数这东西有一个很现实的麻烦不同版本的黄历对同一天的宜忌结论可能不同。比如 2024 年正月初五有的数据源宜“开市”有的宜“祭祀”业务方可能要按内部专家的口径做修正。这个场景不能删行重导正确的做法是按日期做定点 UPDATE。-- 修正 2024-02-14 的宜忌内容 UPDATE calendar_info SET yi JSON_ARRAY(开市, 交易, 祭祀), ji JSON_ARRAY(动土, 安葬), updated_at CURRENT_TIMESTAMP WHERE solar_date 2024-02-14;用JSON_ARRAY(开市, 交易, 祭祀)直接把 JSON 数组写进去比手工拼 JSON 字符串安全得多。注意updated_at是ON UPDATE CURRENT_TIMESTAMP这张表上任何 UPDATE 都会自动刷新所以这里的显式赋值其实可省但列出来能让 SQL 自解释方便以后排查是哪次修正改的数据。6.3 索引与 EXPLAIN百万行量级下的查询计划检查如果数据量真到了百万行级别比如你存了多个版本、多个来源的日历那查询性能必须靠索引和 EXPLAIN 兜底。最常用的索引是uk_solar_date主键索引按日期查天然走唯一索引但如果业务经常按节气查一定要给jieqi字段建索引否则WHERE jieqi 立春会做全表扫描。EXPLAIN SELECT solar_date, lunar_date, yi FROM calendar_info WHERE jieqi 立春 AND solar_date BETWEEN 2020-01-01 AND 2029-12-31;看 EXPLAIN 结果里的type字段const和ref是好的ALL就是全表扫描必须处理。处理方式很简单CREATE INDEX idx_jieqi_date ON calendar_info(jieqi, solar_date);建联合索引。这个索引同时满足按节气过滤和按日期排序是这一场景下的推荐索引设计。我最后说一个习惯导入任何数据之后先跑一遍核对 SQL再抽查两个日期和权威日历比对最后才交付给业务方。这套流程看着啰嗦但每个环节都救过我的命——有一次数据源整体偏移了一天要不是抽检发现了整个业务部门会拿着错误的黄历跑一个月。数据的东西多做一步验证永远不亏希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑