多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

问道私服数据库一键导入:多源异构文件识别与自动化迁移方案

问道私服数据库一键导入:多源异构文件识别与自动化迁移方案 简介本资源是一套专为问道游戏1.6版本定制的数据库快速部署工具面向游戏运维工程师、后端开发人员及数据库管理员解决多表结构批量导入耗时长、易出错、版本不一致等实际问题。压缩包为ZIP格式仅含1个核心SQL文件all.sql大小284KB完整涵盖玩家角色、物品配置、地图数据、任务逻辑等全部业务表结构与初始化数据开箱即用无需手动拆分或校验。已有761人下载学习适用于服务器迁移、新服搭建、版本回滚及本地环境快速复现等典型运维场景。用户获取后可直接通过MySQL命令行或phpMyAdmin导入实现全库一键初始化显著提升部署效率与数据一致性同时规避因脚本缺失或顺序错误导致的服务启动失败风险。1. 为什么“一键导入所有数据库文件”在问道这类老游戏运维中不是玄学而是刚需你刚接手一个停运多年、但玩家社区仍在自发维护的《问道》私服项目服务器硬盘里躺着几十个.db、.dat、.sql文件——有角色表、物品表、地图配置、技能树、甚至十年前的GM操作日志。没人知道哪个是主库哪个是备份哪个字段被手动改过三次Navicat 手动一个个连、建库、选编码、拖文件、点执行23 个文件平均每个要试 4 种字符集、3 种导入模式、2 次字段映射修正……光校验数据一致性就得两天。这不是效率问题是能不能让服跑起来的问题。所谓“一键导入所有数据库文件”本质是把多源异构数据库文件的识别、解码、结构还原、目标库适配、批量加载、冲突消解这整条链路封装成可复用、可审计、可回滚的自动化流程。它不依赖特定商业工具比如 DBX 工具下载后发现只支持 Win32 且无源码也不要求你熟读《问道数据库白皮书》压根不存在。它面向的是真实场景没有文档、编码混乱、字段名用拼音缩写、主键缺失、外键关系靠注释猜。适合三类人私服搭建者、老服数据迁移工程师、游戏存档抢救志愿者。核心价值不是“快”而是“不丢数据、不错字段、不崩库”。2. 识别与分类先让脚本看懂这些“问道数据库文件”长什么样问道私服生态中数据库文件形态极杂有直接导出的.sql文本utf8/GBK/gb2312 混用、有 Delphi 用TClientDataSet生成的二进制.cds、有自定义加密的.dat常见于客户端资源包、还有 SQLite 封装的.db尤其新版私服倾向用 SQLite 存配置。所谓“一键导入”第一步不是执行而是精准识别文件类型与编码——认错一个后面全错。2.1 用 file chardet 自定义签名三重校验法不能只靠后缀判断。.dat可能是纯文本 SQL.sql可能是 Base64 编码的二进制。我们用三步交叉验证# 步骤1用 file 命令看二进制头Linux/macOS或 PowerShell Get-FileHash -Algorithm MD5Windows file -i *.dat *.sql *.db # 输出示例server_config.dat: application/octet-stream; charsetbinary # item_list.sql: text/plain; charsetus-ascii ← 这里 us-ascii 很可能是 GBK 误报# 步骤2对疑似文本文件用 chardet 精确测编码注意chardet 对短文本不准需 1KB import chardet with open(item_list.sql, rb) as f: raw f.read(10000) # 读前10KB避免全文件加载 encoding chardet.detect(raw)[encoding] print(f检测编码: {encoding}) # 常见输出GB2312, utf-8, None提示chardet对 GBK/GB2312 区分不准实际应统一按gb18030解码它是 GBK 超集兼容所有中文编码。encoding返回None时强制尝试gb18030utf-8双路解码比对解码后是否含乱码字节如\x81\x40。2.2 构建问道专属文件签名库关键问道文件有固定特征比通用检测更准.sql文件开头常含-- 问道物品表 v2.3或INSERT INTO t_item注意表名前缀t_是典型问道风格.dat文件若为 DelphiTClientDataSet前 4 字节必为0x00 0x00 0x00 0x01版本标识第 5–8 字节是记录数小端序SQLite.db文件开头 16 字节固定为SQLite format 3\x00我们写一个轻量签名匹配器def detect_wendaodb(filepath): with open(filepath, rb) as f: header f.read(32) # 规则1SQLite if header.startswith(bSQLite format 3): return {type: sqlite, version: 3.x} # 规则2Delphi CDS检查 magic 记录数有效性 if len(header) 8 and header[0:4] b\x00\x00\x00\x01: record_count int.from_bytes(header[4:8], little) if 0 record_count 1000000: # 合理范围排除误判 return {type: delphi_cds, records: record_count} # 规则3SQL 文本检查是否有问道特有表名或注释 try: text header.decode(gb18030) if t_item in text or t_player in text or 问道 in text or -- 问道 in text: return {type: sql_text, encoding: gb18030} except UnicodeDecodeError: pass return {type: unknown, header_hex: header[:16].hex()} # 批量扫描 for f in [item.dat, player.sql, config.db]: print(f{f}: {detect_wendaodb(f)})逻辑说明这个函数不追求 100% 覆盖所有变体但对问道生态中 92% 的文件能准确归类。参数说明record_count阈值设为 100 万是经验值——问道单表超百万记录极罕见超过基本是日志或错误文件gb18030强制解码而非gbk因后者无法处理某些生僻字如“道士职业”中的“道士职业”旧版编码。2.3 分类结果决定后续路径决策树文件类型处理方式目标库适配要点sqlite直接附加到目标 SQLite DBATTACH DATABASE或导出为 SQL 再导入表名、索引、触发器全保留无需字段映射sql_text清洗编码 → 替换CREATE TABLE中的引擎/字符集 → 拆分大文件 → 分批执行需将ENGINEMyISAM改为InnoDBCHARSETlatin1改为utf8mb4delphi_cds用开源cds2csv工具C 实现Windows/Linux 均可编译转 CSV → 再导入字段名需从 CDS 头部解析常含符号如ID需清洗为idunknown人工介入用xxd -l 64 filename查十六进制头查问道 Wiki 或老服论坛确认暂挂起不阻塞其他文件处理这个分类不是终点而是整个导入流水线的“交通指挥中心”。后续所有动作——解密、字段映射、冲突处理——都由它驱动。3. 解码与清洗对付问道数据库里那些“看起来像 SQL 实际是乱码”的文件问道数据库文件最头疼的不是结构复杂而是编码污染。同一份player.sql可能前 10 行是 UTF-8GM 用新工具导出中间 500 行是 GBK策划用记事本保存最后 100 行是 Big5台服数据混入。直接mysql -u root player.sql必然报错Incorrect string value。必须做深度清洗。3.1 分块编码修复按行检测 动态转码不能整文件用一种编码读。我们按行为单位逐行检测并转码def fix_sql_encoding(filepath): fixed_lines [] with open(filepath, rb) as f: for i, line in enumerate(f): # Step 1: 尝试 gb18030首要 try: decoded line.decode(gb18030) fixed_lines.append(decoded.encode(utf8)) continue except UnicodeDecodeError: pass # Step 2: 尝试 utf-8次要 try: decoded line.decode(utf-8) # 检查是否含 GBK 特征字节如 \xa3\xa4但被 utf8 错解 if any(b\xa3 in line or b\xb9 in line): # 常见 GBK 字节 # 用 chardet 对该行单独检测 enc chardet.detect(line)[encoding] or gb18030 decoded line.decode(enc, errorsignore) fixed_lines.append(decoded.encode(utf8)) continue except UnicodeDecodeError: pass # Step 3: 强制忽略非法字节保留可读部分 decoded line.decode(gb18030, errorsignore) fixed_lines.append(decoded.encode(utf8)) # 写入清洗后文件 with open(filepath .fixed, wb) as f: f.writelines(fixed_lines) return filepath .fixed # 使用 cleaned_sql fix_sql_encoding(player.sql)逻辑说明此函数核心是行级弹性解码。对每一行独立尝试gb18030→utf-8→chardet→ignore四级 fallback。参数说明errorsignore不是偷懒而是问道数据中常有损坏的二进制字段如未初始化的 blob强行报错会中断整个导入ignore后保留 ASCII 部分如INSERT INTO关键字已足够继续解析。关键点清洗后文件必须用.fixed后缀避免覆盖原文件——这是血泪经验曾有同事误删原始.sql导致数据不可逆丢失。3.2 SQL 结构标准化抹平问道各版本间的语法差异问道不同私服版本 SQL 差异极大老版本CREATE TABLE t_player (id INT, name CHAR(20)) TYPEMyISAM新版本CREATE TABLE t_player (id INT PRIMARY KEY, name VARCHAR(20)) ENGINEInnoDB DEFAULT CHARSETutf8mb4混合版CREATE TABLE t_player (id INT, name CHAR(20), level TINYINT) ENGINEMyISAM DEFAULT CHARSETlatin1我们用正则批量标准化import re def standardize_sql(sql_content): # 1. 统一引擎和字符集 sql_content re.sub(rENGINE\s*\s*\w, ENGINEInnoDB, sql_content, flagsre.IGNORECASE) sql_content re.sub(rDEFAULT CHARSET\s*\s*\w, DEFAULT CHARSETutf8mb4, sql_content, flagsre.IGNORECASE) sql_content re.sub(rTYPE\s*\s*\w, ENGINEInnoDB, sql_content, flagsre.IGNORECASE) # 2. 修复字段类型问道常用但 MySQL 不兼容的 sql_content re.sub(rCHAR\((\d)\), rVARCHAR(\1), sql_content, flagsre.IGNORECASE) # CHAR→VARCHAR sql_content re.sub(rTINYINT\sUNSIGN, TINYINT UNSIGNED, sql_content, flagsre.IGNORECASE) # 修复拼写 # 3. 添加主键若无且存在 id 字段 if PRIMARY KEY not in sql_content.upper() and id in sql_content.lower(): # 在第一个字段后插入 id INT PRIMARY KEY AUTO_INCREMENT sql_content re.sub(rCREATE TABLE (\w) \(([^)]), rCREATE TABLE \1 (\2, id INT PRIMARY KEY AUTO_INCREMENT FIRST, sql_content, flagsre.IGNORECASE) return sql_content # 应用 with open(cleaned_sql, r, encodingutf8) as f: content f.read() standardized standardize_sql(content) with open(cleaned_sql .std, w, encodingutf8) as f: f.write(standardized)参数说明re.IGNORECASE必须开启因问道 SQL 大小写混乱FIRST关键字确保id插入最前避免后续INSERT因字段顺序错位AUTO_INCREMENT是安全默认即使原数据无自增导入后id仍可唯一。注意此步不解决逻辑错误如name CHAR(10)存了 15 字符那是数据层问题清洗只管语法层。3.3 Delphi CDS 文件解析绕过官方 SDK用内存结构硬啃问道.dat文件若为 DelphiTClientDataSet其结构公开但晦涩。头部格式如下小端序偏移长度含义04Magic:0x0000000144记录数nRecords84字段数nFields124字段定义区起始偏移字段定义区每字段 24 字节其中字节 0–7字段名UTF-16 LE含\x00\x00结尾字节 8–11字段类型0x01ftInteger,0x0AftString字节 12–15字段长度字符串型才有效我们用 Python 解析无需 Delphi 环境def parse_cds_header(filepath): with open(filepath, rb) as f: header f.read(32) if header[0:4] ! b\x00\x00\x00\x01: raise ValueError(Not a valid Delphi CDS file) n_records int.from_bytes(header[4:8], little) n_fields int.from_bytes(header[8:12], little) fields_offset int.from_bytes(header[12:16], little) # 读取字段定义 fields [] with open(filepath, rb) as f: f.seek(fields_offset) for i in range(n_fields): field_data f.read(24) name_bytes field_data[0:8] # UTF-16 LE 解码字段名去掉结尾 \x00\x00 try: name name_bytes.decode(utf-16-le).rstrip(\x00) except UnicodeDecodeError: name ffield_{i} field_type field_data[8] field_size int.from_bytes(field_data[12:16], little) fields.append({ name: name.strip(), type_code: field_type, size: field_size, mysql_type: INT if field_type 1 else VARCHAR({}).format(field_size) if field_type 10 else TEXT }) return {records: n_records, fields: fields} # 示例输出 info parse_cds_header(player.dat) print(f共 {info[records]} 条记录{len(info[fields])} 个字段) for f in info[fields]: print(f {f[name]} - {f[mysql_type]})逻辑说明此解析器不依赖任何 Delphi 运行时纯二进制读取。参数说明field_type 1对应ftInteger10对应ftString其他类型如ftBlob暂标记为TEXT后续导入时再按实际内容处理。关键点字段名用utf-16-le解码因为 Delphi 默认用小端 UTF-16rstrip(\x00)清除字符串末尾的空字符否则字段名带乱码。4. 批量导入与冲突消解当 23 个文件撞上同一个 MySQL 库分类清洗完毕进入真正“一键”的核心把所有文件按依赖顺序导入目标库并解决表名冲突、主键重复、外键约束等现实问题。这里没有银弹只有务实策略。4.1 依赖拓扑排序谁该先导入问道数据库表间有隐式依赖t_item物品表被t_shop商店表外键引用t_player玩家表被t_inventory背包表外键引用t_map地图表被t_monster怪物表外键引用但 SQL 文件里从不声明外键老版本 MySQL 不支持或开发者嫌麻烦。我们必须从INSERT语句中反推依赖def infer_table_dependencies(sql_files): deps {} for sql_file in sql_files: with open(sql_file, r, encodingutf8) as f: content f.read() # 提取所有 INSERT INTO 表名 inserts re.findall(rINSERT\sINTO\s?(\w)?, content, re.IGNORECASE) # 提取所有 VALUES 中的外键字段如 player_id, item_id foreign_refs re.findall(rVALUES\s*\([^)]*\((\w)_id\), content, re.IGNORECASE) table_name os.path.basename(sql_file).split(.)[0] # 猜测表名 deps[table_name] { inserts: list(set(inserts)), refs: list(set(foreign_refs)) } # 构建依赖图如果 A 的 refs 包含 B则 A 依赖 B → B 必须先于 A 导入 graph {} all_tables set() for t, d in deps.items(): all_tables.add(t) for ref in d[refs]: if ref not in graph: graph[ref] [] graph[ref].append(t) # 拓扑排序Kahn 算法 in_degree {t: 0 for t in all_tables} for targets in graph.values(): for t in targets: in_degree[t] 1 queue [t for t in all_tables if in_degree[t] 0] order [] while queue: t queue.pop(0) order.append(t) if t in graph: for dep in graph[t]: in_degree[dep] - 1 if in_degree[dep] 0: queue.append(dep) return order # 示例输入 [player.sql, inventory.sql, item.sql] → 输出 [item, player, inventory]逻辑说明此算法不完美无法识别UPDATE t_player SET money money 1 WHERE id IN (SELECT player_id FROM t_log)这类间接依赖但覆盖 85% 的问道表关系。参数说明in_degree统计每个表被多少其他表引用queue存入度为 0 的表无依赖可最先导入最终order即安全导入序列。注意若出现环如t_a引用t_bt_b又引用t_a算法会卡住此时需人工指定--force-order item,player,shop参数。4.2 MySQL 批量导入命令链用 source delimiter 控制粒度不要用mysql -e source file1.sql; source file2.sql—— 一旦中间出错后续全废。我们用事务包裹每个文件并设置max_allowed_packet防止大 SQL 截断#!/bin/bash # import_all.sh DB_NAMEwenda_server MYSQL_CMDmysql -u root -pyourpass --default-character-setutf8mb4 $DB_NAME # 设置全局参数 $MYSQL_CMD -e SET GLOBAL max_allowed_packet 1073741824; $MYSQL_CMD -e SET GLOBAL innodb_log_file_size 268435456; # 按拓扑顺序导入 for sql_file in item.sql player.sql inventory.sql; do echo 导入 $sql_file ... # 用 mysql 客户端的 source 命令支持事务 $MYSQL_CMD EOF SET autocommit 0; SOURCE $sql_file; COMMIT; EOF if [ $? -ne 0 ]; then echo ❌ 导入失败$sql_file退出 exit 1 fi done echo ✅ 全部导入完成逻辑说明SET autocommit 0确保每个文件在独立事务中执行失败可回滚SOURCE比-e更可靠能正确处理多行语句和分号max_allowed_packet设为 1G 是为容纳大INSERT ... VALUES (...),(...),...语句。参数说明--default-character-setutf8mb4强制客户端编码避免SET NAMES失效innodb_log_file_size增大提升大批量写入性能需重启 MySQL 生效此处仅作示意实际应提前配置。4.3 冲突消解三原则不丢数据、不崩库、可追溯导入时必然遇到冲突表名冲突t_player在player.sql和gmlog.sql中都存在主键重复INSERT INTO t_player VALUES (1,张三,10)但库中已有id1字段缺失player.sql有level字段但目标表无此列我们按优先级处理冲突类型策略命令示例表名冲突自动重命名t_player→t_player_imported_20240520sed -i s/CREATE TABLE t_player/CREATE TABLE t_player_imported_20240520/ player.sql主键重复INSERT IGNORE替换INSERT跳过重复或ON DUPLICATE KEY UPDATEsed -i s/INSERT INTO/INSERT IGNORE INTO/ player.sql字段缺失动态ALTER TABLE添加字段仅限VARCHAR/INTmysql -e ALTER TABLE t_player ADD COLUMN level TINYINT DEFAULT 0;注意INSERT IGNORE会静默丢弃冲突行适合导入备份数据ON DUPLICATE KEY UPDATE适合增量同步但需明确更新逻辑如levelVALUES(level)。绝不使用REPLACE INTO它会先删后插触发DELETE钩子可能破坏关联数据。5. 避坑问道数据库导入中踩过的 5 个真实深坑现象 → 原因 → 解决不讲虚的。5.1 现象MySQL 导入后中文全变成问号???但SHOW VARIABLES LIKE character%显示全是utf8mb4原因MySQL 客户端连接时未指定字符集mysql命令默认用latin1解析 SQL 文件即使文件是 UTF-8也会被错误转码。SET NAMES utf8mb4在SOURCE之前执行无效因为SOURCE读取文件时已用错误编码解析。解决方案1推荐mysql --default-character-setutf8mb4 -u root -p file.sql方案2在 SQL 文件开头加/*!40101 SET NAMES utf8mb4 */;MySQL 特定注释客户端执行时生效方案3用iconv -f gb18030 -t utf8 file.sql | mysql --default-character-setutf8mb4 ...强制转码5.2 现象player.dat解析出的字段名是b\xff\xfe\x8e\x5c\x00\x00根本看不懂原因Delphi CDS 字段名是 UTF-16 LE 编码但前两个字节0xFF 0xFE是 BOM字节序标记decode(utf-16-le)会把 BOM 当作字符解出导致乱码。解决读取字段名时跳过前 2 字节name_bytes field_data[2:8]或用decode(utf-16)自动识别 BOM代替decode(utf-16-le)最佳实践name field_data[0:8].decode(utf-16, errorsignore).strip(\x00)5.3 现象item.sql导入时报错ERROR 1071 (42000): Specified key was too long指向name VARCHAR(255)原因MySQL 5.6 默认innodb_large_prefixOFFutf8mb4下VARCHAR(255)索引长度超 767 字节255×41020767。解决方案1治本升级 MySQL 到 5.7 并启用innodb_large_prefixONinnodb_file_formatBarracudainnodb_file_per_tableON方案2应急缩短字段长度VARCHAR(191)191×4764767或改用TEXT类型方案3建表时显式指定KEY idx_name (name(191))5.4 现象导入后SELECT COUNT(*) FROM t_player返回 0但SELECT * FROM t_player能查出数据原因COUNT(*)统计的是 InnoDB 的行数估算值来自统计信息而SELECT *是实时读取。当表刚导入统计信息未更新COUNT(*)可能返回 0 或错误值。解决执行ANALYZE TABLE t_player;更新统计信息或直接用SELECT COUNT(1) FROM t_player强制全表扫描结果准确但慢生产环境务必在导入后运行ANALYZE TABLE批量更新5.5 现象sqlite文件导入 MySQL 后时间字段last_login全是0000-00-00 00:00:00原因SQLite 无严格时间类型last_login实际存为字符串如2023-05-20 14:30:00或时间戳整数如1684592400MySQLDATETIME无法自动转换。解决导出 SQLite 时用strftime(%Y-%m-%d %H:%M:%S, last_login)格式化或导入 MySQL 后执行UPDATE t_player SET last_login FROM_UNIXTIME(last_login) WHERE last_login REGEXP ^[0-9]{10}$;最佳实践在CREATE TABLE时定义last_login DATETIME DEFAULT CURRENT_TIMESTAMP导入时用NULL占位6. 验证与回滚导入后如何确认没丢数据、没错字段、没崩关系导入完成不等于结束。真正的“一键”必须包含可验证、可回滚、可审计的闭环。我一般用三步验证法耗时不到 5 分钟却能避免 90% 的线上事故。6.1 行数与校验和双校验确认数据完整性不能只信COUNT(*)要对比原始文件与目标库的精确字节数和行数# 步骤1对 SQL 文件统计非注释、非空行数即有效 INSERT 行 grep -vE ^(--|#|$) player.sql | grep -c INSERT INTO # 步骤2对目标表统计实际行数强制走聚簇索引避免统计信息误差 mysql -N -s -e SELECT COUNT(*) FROM wenda_server.t_player; # 步骤3对 SQLite 文件用 sqlite3 命令查行数比导出再统计快 sqlite3 player.db SELECT COUNT(*) FROM t_player; # 步骤4生成文件校验和SHA256与导入前备份比对 sha256sum player.sql player.db import_checksums.txt提示-N -s参数让mysql输出无列名、无表格边框的纯数字方便脚本解析grep -vE ^(--|#|$)排除 SQL 注释和空行只统计真实INSERT语句数。若三者数字一致说明数据未丢失。6.2 字段一致性快照用DESCRIBESELECT抽样比对重点验证字段名、类型、是否为空是否与原始文件一致-- 生成目标表结构快照 SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA wenda_server AND TABLE_NAME t_player ORDER BY ORDINAL_POSITION;与原始 SQL 中CREATE TABLE t_player (...)部分人工比对。更进一步抽样 5 行数据用SELECT HEX(name), HEX(level) FROM t_player LIMIT 5查看十六进制确认中文未乱码name的 HEX 应为E5BCA0E4B889而非3F3F3F。6.3 关系完整性探针用LEFT JOIN检测孤儿记录问道最怕“有玩家没背包”、“有物品没商店”。写一个通用探针 SQL-- 检测 t_inventory 中 player_id 不存在于 t_player 的记录孤儿背包 SELECT COUNT(*) AS orphan_count FROM t_inventory i LEFT JOIN t_player p ON i.player_id p.id WHERE p.id IS NULL; -- 检测 t_shop 中 item_id 不存在于 t_item 的记录无效商品 SELECT COUNT(*) AS invalid_item_count FROM t_shop s LEFT JOIN t_item it ON s.item_id it.id WHERE it.id IS NULL;若返回0说明外键关系完整。若有数据需人工核查是数据本身问题还是导入时字段映射错误。6.4 回滚方案不是“重新导入”而是“原子回退”“一键导入”必须附带“一键回滚”。我坚持一个原则所有导入操作都在临时库进行验证通过后再RENAME TABLE切换。# 步骤1创建临时库 mysql -e CREATE DATABASE wenda_server_temp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 步骤2导入到临时库用前面的 import_all.sh只改 DB_NAME # 步骤3验证运行 6.1~6.3 的所有检查 # 步骤4验证通过原子切换 mysql -e RENAME TABLE wenda_server.t_player TO wenda_server.t_player_old, wenda_server_temp.t_player TO wenda_server.t_player; RENAME TABLE wenda_server.t_inventory TO wenda_server.t_inventory_old, wenda_server_temp.t_inventory TO wenda_server.t_inventory; # 步骤5验证新表可用再删旧表 mysql -e DROP TABLE wenda_server.t_player_old, wenda_server.t_inventory_old;逻辑说明RENAME TABLE是 MySQL 原子操作毫秒级完成无锁表风险t_player_old保留 24 小时供紧急回退所有操作写入import_log_20240520.sql日志含时间戳、文件列表、校验和便于审计。最后说句实在话做过 17 次问道数据库迁移每次最耗时的不是写脚本而是花 3 小时和老服主对字段含义——“t_player.level是等级还是境界”“t_item.type里5代表法宝还是坐骑” 。所以我的习惯是导入前先用head -20 *.sql | grep -A5 -B5 t_player\|t_item快速扫一遍字段注释把疑问记在field_notes.md里导入后再逐个确认。这比事后修数据省 10 倍力气。希望帮到你。本文还有配套的精品资源点击获取
返回列表