ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

省市区MySQL数据表:行政区划码与经纬度实战指南

省市区MySQL数据表:行政区划码与经纬度实战指南 简介一份面向开发者、数据分析师和地图应用相关从业者的中国省市区城市列表MySQL数据表资源。数据表涵盖全国省市县三级行政区域包含标准行政区划码与经纬度坐标可直接通过SQL脚本导入MySQL数据库使用免去手工整理行政区数据的繁琐流程适合用于地图、物流、电商地址补全、数据分析等场景。压缩包内仅含1个SQL脚本文件整体大小约103KB导入后即可生成结构完整的行政区数据表。该资源已有1928人学习下载得到较多开发者的实际检验。借助这份数据用户可以快速搭建地理位置相关功能例如基于经纬度的距离计算、区域检索、收货地址自动匹配或按行政区划进行统计分析与可视化展示。对于任何需要处理中国省市区层级信息的项目来说这份数据表都能显著降低前期数据准备成本提升开发与交付效率。1. 一份省市区 mysql 数据表省掉三级联动的大部分加班时间做电商后台或地图可视化时我踩得最多的不是业务逻辑而是省市区数据。网上找的 Excel 要么缺县级要么经纬度是摆设最后还得自己清洗。换成这份省市区城市列表 mysql 数据表之后导入数据库就能直接查转成 JSON 数据就能给前端每条记录都带行政区划码和经纬度。它解决的是最具体的三类需求城市选择器、订单区域报表、配送范围判断。适合后端和全栈开发尤其是那些不想每个月手工维护行政区划的人。2. 表结构设计为什么以行政区划码和经纬度为核心拿到 SQL 文件先别急着 source把表结构看懂后面转 JSON 和排错才不慌。这张表看起来字段不多但每一列都按生产需求踩过坑之后定的尤其是行政区划码和经纬度决定了你半年后还能不能继续用这份数据。2.1 行政区划码是国标自增 id 不是省市区数据最值钱的字段是行政区划码。国家统一维护的六位数字编号省和自治区用两位省码地级市用前四位市码区县用完整六位。比如 320000 是江苏省320500 是苏州市底下每个区县再往后排。用 adcode 做唯一键等于直接用了国标编号对接高德、腾讯、统计平台时都能直接把编码传过去。自增 id 只表达插入顺序换一份新数据包id 全部重排前端已经保存的 city_id 全都对不上旧记录。所以这张表一般保留自增 id 做外键关联但业务判断一律用 adcode。排序也直接ORDER BY adcode出来的就是国标顺序做下拉联动不用再额外折腾排序字段。省市区表的访问模式基本是“按父级查子级”所以 parent_id 上一定要建普通索引区域订单统计 join 进来时才不会全表扫这是这张表最简单也最有效的性能调优点。2.2 经纬度字段DECIMAL(10,6) 和坐标系缺一不可经纬度字段最常见的错误是用 float。float 在高德地图上放大到街道级别就会飘而且 DECIMAL 在导出 JSON 给前端时更好控制精度。lng 用DECIMAL(10,6)lat 同理整数部分 4 位、小数 6 位足够覆盖中国范围一百三十多度的经度。6 位小数对应的地面精度约 0.1 米省市区中心点根本用不到更高精度。比精度更要命的是坐标系。常见省市区数据里的 lng/lat 有两个来源高德、阿里系接口导出的 GCJ-02和 GPS、WGS-84 原始坐标。同一个点在两种坐标系下相差几十到几百米所以我会在表里加一个 coordinate_system 字段至少在建表注释里写清楚。像“阿里地图省市区经纬度 json”这类公开源基本是 GCJ-02而 ArcGIS 里 Display XY 默认按 WGS-84 显示这就是为什么有人导入后点全偏了。后面避坑章会专门展开。2.3 先落 MySQL 还是先出 JSON按消费方决定后端做区域统计、权限范围判断就必须落 MySQL前端做省市区联动选择器通常要的是嵌套 JSON。省市区数据总量只有几千行两套形态都保留的成本很低。常见做法是把 SQL 文件作为唯一数据源再写一个脚本定期导出 JSON 给接口和前端离线包。MySQL 5.7 之后的 json 查询函数能在 SQL 层做 JSON 筛选但要组装省市区树在应用层处理最直观。如果你还要街道级数据就按同一套格式建第二张 area_street 表parent_id 指向区县 id这就是 MySQL 创建多个数据表的常见格式比把所有层级混在一张表里好维护得多。3. 导入 MySQL 三步走建表、导数据、验证结果从拿到 SQL 文件到真正能查我习惯拆成三步先建表再导入最后验证。前两步大部分人都会第三步才是避免“导入成功但数据是脏的”的关键。3.1 用这段 DDL 建表字段类型一次定对CREATE DATABASE IF NOT EXISTS region DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE region; CREATE TABLE china_area ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, name VARCHAR(50) NOT NULL COMMENT 省/市/区名称, level TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 1省级 2市级 3区县级, parent_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 父级id省级填0, adcode CHAR(6) NOT NULL DEFAULT COMMENT 行政区划码, lng DECIMAL(10,6) NOT NULL DEFAULT 0 COMMENT 经度, lat DECIMAL(10,6) NOT NULL DEFAULT 0 COMMENT 纬度, PRIMARY KEY (id), UNIQUE KEY uk_adcode (adcode), KEY idx_parent_id (parent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT中国省市区基础表;这段 DDL 里几个参数是长期用出来的字符集必须 utf8mb4不能用老的 utf8实际数据里偶尔会出现生僻地名和特殊字符adcode 用 CHAR(6) 而不是 VARCHAR长度固定省得脏数据里夹空格parent_id 默认值用 0 表示省级比 NULL 好写查询。level 用 TINYINT 足够三级以内不需要更大空间。如果你在 MySQL Workbench 里操作先连接数据库再选中 region schema 执行命令行也一样先 USE region避免表建到别的库下面。为了应对行政调整这里加了 mysql 默认值为 0 的写法省级记录 parent_id 全部是 0。3.2 mysql 命令行导入 SQL 数据文件拿到数据包里的 SQL 文件后最直接的导入方式是 shell 重定向mysql -uroot -p --default-character-setutf8mb4 -h127.0.0.1 region /data/china_area.sql如果数据包里有多张表比如区县表和街道表分开导入顺序按父子层级来先导省市区再导街道避免外键字段还没建就插数据。如果已经在 mysql 客户端里也可以直接写USE region; SOURCE /data/china_area.sql;这里有两个参数值得记住--default-character-setutf8mb4要和 SQL 文件的编码一致文件是 GBK 就先转成 UTF-8 再导否则中文必乱-h127.0.0.1强制走 TCP绕开 socket 文件找不到的问题。导入过程如果出现 warning 不要跳过尤其是 “Incorrect value” 这类提示后面验证阶段会变成脏数据。几千行的省市区表导入基本是秒级卡住多半是字符集转换或者服务器配置太低。3.3 导入后验证查层级、查编码、查经纬度范围SELECT level, COUNT(*) AS cnt FROM china_area GROUP BY level ORDER BY level; SELECT id, name, adcode, lng, lat FROM china_area WHERE parent_id 0 ORDER BY adcode; SELECT COUNT(*) AS total, SUM(lng BETWEEN 73 AND 135) AS lng_ok, SUM(lat BETWEEN 3 AND 54) AS lat_ok FROM china_area;第一个查询看三层数量是否正常省一级大概 31 个市一级三百多个区县一级两千多个。直辖市在表里也是按省级存不要单独造一个奇怪的“北京市→北京市→东城区”三层结构。第二个查询抽查省级记录的 adcode 和中文名看有没有乱码。第三个查询是最容易漏的中国大致范围是经度 73 到 135、纬度 3 到 54lng_ok 和 lat_ok 如果明显小于 total说明这批经纬度里有 0 值或者境外数据得回到数据源核对。4. 转 JSON从 MySQL 表到前端可用的嵌套数据MySQL 表只是数据底座真正给前端用的往往是 JSON。转 JSON 不是把查询结果塞进 json.dump 那么简单编码、Decimal 精度、父子关系每一步都可能让接口直接翻车。4.1 先导扁平 JSON查库并处理 Decimal 精度# -*- coding: utf-8 -*- import pymysql import json def to_plain(row): return { id: row[id], name: row[name], level: row[level], parent_id: row[parent_id], adcode: row[adcode], lng: float(row[lng]), lat: float(row[lat]), } conn pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databaseregion, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, ) try: with conn.cursor() as cur: cur.execute( SELECT id, name, level, parent_id, adcode, lng, lat FROM china_area ORDER BY adcode ) rows [to_plain(r) for r in cur.fetchall()] finally: conn.close() with open(china_area_flat.json, w, encodingutf-8) as f: json.dump(rows, f, ensure_asciiFalse, indent2)这里最容易踩的是 pymysql 返回的 DECIMAL 字段是 Decimal 类型直接 json.dump 会报 TypeError: Object of type Decimal is not JSON serializable所以必须 float 化。charsetutf8mb4 保证从数据库到 Python 这一段中文不乱码json.dump 里的 ensure_asciiFalse 保证落盘文件里不是一堆 \uXXXX 转义indent2 方便调试接口用的版本可以改成 indentNone 减小体积。扁平 JSON 适合前端自己用 groupBy 做两级联动不想二次聚合时可以直接给。4.2 组装省市区嵌套 JSON按 parent_id 归并不按 adcode 截断with open(china_area_flat.json, r, encodingutf-8) as f: rows json.load(f) areas {row[id]: {**row, children: []} for row in rows} tree [] for area in areas.values(): if area[level] 1: tree.append(area) else: parent areas.get(area[parent_id]) if parent is None: # 兜底父级缺失的孤儿节点单独挂根避免静默丢数据 area[orphan] True tree.append(area) else: parent[children].append(area) def sort_by_adcode(node): node[children].sort(keylambda x: x[adcode]) for child in node[children]: sort_by_adcode(child) for node in tree: sort_by_adcode(node) with open(china_area_tree.json, w, encodingutf-8) as f: json.dump(tree, f, ensure_asciiFalse, indent2)组装树的关键是按 parent_id 找父节点而不是按 adcode 前四位去截。省直辖县级市、直辖市这些特殊层级截 adcode 经常挂错父级这是地区数据里最典型的坑。用 id 建字典后每个节点直接找 parent_id 对应的对象没找到就标记 orphan 并挂到根上这样数据有问题时你能一眼看见而不是前端选择器里静默少了一个区。递归排序只需要三层深度Python 默认递归限制不会触发但排序逻辑还是写在函数里更清晰。如果你用 MySQL 8也可以直接用 JSON_TABLE 这类 json 查询函数在 SQL 层做筛选但组装这种固定深度的树应用层做起来快且好调试。4.3 转 JSON 时容易忽视的三件事编码、键名、体积第一件是编码导出文件必须用 UTF-8 写Windows 上默认 GBK 会让 JSON 文件打开就是乱码。第二件是键名adcode、name、level、parent_id 作为接口字段已经足够语义化如果前端用 el-cascader最好导出时直接生成 value 和 label 两个键省得前端再写一层映射。第三件是体积省市区树结构全量 JSON 一般几十 KB放到静态资源里做离线包完全没问题但如果接口每次实时组装就要考虑在数据库之外做一层缓存别让同一棵树在每个请求里重建一遍。前端拿到对象后需要字符串化缓存用的就是 JSON.stringify和导出端的格式没有冲突。5. 避坑排查导入和转换过程中 5 个典型踩坑记录这块写的是实际用省市区数据表最容易翻车的地方每条按现象、原因、解决展开遇到同类问题可以直接照抄。5.1 ERROR 2002 (HY000)MySQL 服务没起来或 socket 路径不对现象执行 mysql 命令连接本地时报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因MySQL 服务没启动或者 my.cnf 里 socket 路径不是默认 /tmp/mysql.sock。本机装了多个 MySQL 版本时也会出现客户端默认找的 socket 和服务端不是同一个。解决先systemctl status mysqld或service mysqld status看服务是否在跑没跑就systemctl start mysqld。连接时加-h127.0.0.1强制走 TCP绕开 socket 路径问题。如果是刚按 mysql 安装配置教程装完还没初始化启动前要确认 data 目录已生成否则 service 起来也会闪退。Workbench 连接时同样填 127.0.0.1连接方式选 TCP/IP。5.2 导入后中文全部变成问号或乱码现象导入完成后SELECT name FROM china_area LIMIT 5返回 ??? 或锟斤拷。原因SQL 文件本身是 GBK 编码连接字符集却用了 utf8mb4或者表建成了 latin1。数据包里的中文在账号体系里非常常见编码不匹配必然乱。解决先SHOW CREATE TABLE china_area;确认 DEFAULT CHARSETutf8mb4。文件是 GBK 就转码后再导入iconv -f GBK -t UTF-8 china_area.sql china_area_utf8.sql mysql -uroot -p --default-character-setutf8mb4 region china_area_utf8.sql转码后立刻抽查几个省名和城市名不要只看导入是否报错。这里最容易骗人因为 GBK 转 UTF-8 偶尔不报错但生僻字已经被替换成问号问题要到业务层才暴露。5.3 经纬度在地图上全偏了GCJ-02 与 WGS-84 坐标系混用现象数据导入 ArcGIS Pro 后用 Display XY 显示点位跑到几百米外或者同一个点在高德和天地图上位置不一致。原因省市区坐标大多来自高德、阿里地图这些是 GCJ-02 火星坐标系而 ArcGIS 和 GPS 设备默认 WGS-84。两个坐标系之间不是简单加减直接用必然偏。解决先在数据源文档里确认 coordinate_system。如果你的底图是高德GCJ-02 反而更准不要强行转 WGS-84如果要导入 ArcGIS 或与 GPS 轨迹叠加必须做坐标纠偏。少量点用 xy 坐标转换经纬度工具手工处理可以几千个点必须跑脚本批量转换生产环境不要在前端展示层做转换否则图表工具查坐标也会对不上。5.4 接口返回 JSON 后 Java 反序列化报 Date 错误现象后端接导出 JSON 时抛json parse error: cannot deserialize value of type java.util.Date from String。原因JSON 里带了 created_at、updated_at 或某个日期字段字符串格式和 Java 类里 Date 字段的预期格式不一致。省市区表本身只需要行政区划码和经纬度导入时多余的时间字段被保留下来交付物就不干净。解决导出脚本里直接去掉时间字段或者 SQL 里用 DATE_FORMAT 统一成yyyy-MM-dd HH:mm:ss。如果接口层必须保留日期就在 DTO 字段上配JsonFormat(patternyyyy-MM-dd HH:mm:ss, timezoneGMT8)。这个错和表结构本身无关是 JSON 转换时字段类型不匹配导致的排查时先看报错字段名别去翻地图代码。5.5 子级挂错或成孤儿行政区划和父子关系不匹配现象某个区县在三级联动里没出现在它所属城市下面或者 left join 父表后 parent 为 NULL。原因数据源来自不同年份版本遇到省直辖县级市、直辖市下无地级市、撤县设区等调整直接截 adcode 前四位会得到错误的城市码。行政区划每年都在微调拿到旧包再叠加新业务问题更容易集中爆发。解决先跑孤儿数据检查把父级缺失的节点揪出来SELECT a.adcode, a.name, a.parent_id FROM china_area a LEFT JOIN china_area p ON a.parent_id p.id WHERE a.level 1 AND p.id IS NULL;确认是数据问题后用 mysql update 语法修正 parent_id改之前先CREATE TABLE china_area_bak AS SELECT * FROM china_area;备份再执行 UPDATE。千万别在没备份的情况下直接改父子关系一次失误会把整张表的结构带偏。6. 一条 SQL 做完全表体检再养一个离线 JSON 的更新习惯最后这一步不是锦上添花是判断这份省市区数据表值不值得长期用的标准。6.1 一条 SQL 给省市区表做体检SELECT COUNT(*) AS total_rows, COUNT(DISTINCT adcode) AS uniq_codes, SUM(level 1) AS province_cnt, SUM(level 2) AS city_cnt, SUM(level 3) AS district_cnt, SUM(lng BETWEEN 73 AND 135 AND lat BETWEEN 3 AND 54) AS coord_ok FROM china_area;total_rows 等于 uniq_codes说明 adcode 没有重复province_cnt、city_cnt、district_cnt 的数量级符合预期说明层级结构完整coord_ok 接近 total_rows说明经纬度没有大范围脏点。这条 SQL 每次拿到新数据表都要先跑跑完再决定要不要更新到线上库。6.2 进阶用法把导出脚本变成习惯而不是一次性的活我现在维护这种数据表的习惯是每次行政区划调整后重新跑一遍第 4 章的导出脚本文件名带版本号例如 region_2026_v1.json接口和前端按版本号加载。数据表再可靠也扛不住人肉改文件脚本化输出才是长期可控的做法。真正决定这份省市区数据表值不值得用的不是导入快不快而是行政区划码和经纬度字段能不能支撑你后续一年内的功能演进。如果你也要做省市区功能早点把脚本和版本命名习惯养起来比我当初手动改 JSON 省心太多。希望帮到你。本文还有配套的精品资源点击获取
返回列表