简介这份资源面向需要做地区维度数据存储与查询的开发者提供全国省、市、区三级行政区划及其经纬度坐标的 MySQL 数据可用于地址库搭建、地图打点、区域统计、配送范围计算等场景适合后端开发、数据分析与 GIS 入门人员直接导入使用。压缩包共 1 个文件为单个 sql 脚本整体约 127KB体量轻便导入后即可得到结构化的省市区层级表与对应经纬度字段省去逐级手工整理与坐标采集的麻烦。目前已有 2998 人学习下载说明该数据在同类需求中具备一定参考价值。读者可直接获得现成的行政区划层级关系与坐标数据便于快速建立地区字典表、支撑按省市区的分组查询与前端级联选择也能作为地图可视化、区域热力统计的基础数据源减少重复造轮子的时间成本。1. 全国省市区经纬度 MySQL 数据一份能直接落地的行政区划坐标库做 LBS 相关开发的人大概都遇到过这种场景用户填完地址前端要在地图上打个点后端却只有一串「省市区」文本没有坐标。临时调第三方地理编码接口吧量一大就被限流还按次计费自己攒吧全国三千多个区县、四万多个乡镇街道的经纬度靠手工整理根本不现实。这份「全国省市区经纬度 MySQL 数据」解决的正是这个痛点——它把行政区划层级和对应的经纬度坐标预先整理成可直接导入 MySQL 的结构化数据省掉接口调用和清洗环节。适合做门店管理、物流分单、区域统计、地图选点、地址库建设的后端和全栈开发者也适合需要离线坐标数据、不想依赖外部服务的数据分析场景。下面按「数据长什么样、怎么导、怎么查、坑在哪」的顺序拆开讲。2. 数据结构与字段设计先看懂表再动手拿到一份 SQL 或 CSV 数据最忌讳上来就source一把梭。先搞清楚它的层级关系、主键设计和坐标精度后面查询和关联才不会翻车。这类行政区划坐标数据通常有两种组织方式一种是单表自关联idparent_id另一种是省、市、区三张表分开。前者灵活、便于递归查询后者查询直观、join 写起来简单。我一般优先选自关联单表因为乡镇街道这一级加上去之后三表结构会变得很别扭。2.1 典型表结构与字段含义一份合格的省市区经纬度表核心字段大致如下字段名各家可能略有差异导入前务必DESC一下确认字段类型说明idINT / BIGINT行政区划主键通常沿用官方区划代码parent_idINT上级区划 id省级为 0 或 NULLnameVARCHAR(64)区划名称如「某区」「某县」levelTINYINT层级1 省 / 2 市 / 3 区县 / 4 乡镇街道lngDECIMAL(10,6)经度保留 6 位小数约 0.1 米精度latDECIMAL(10,6)纬度pinyinVARCHAR(64)拼音或首字母做搜索联想时用得上这里有两个设计细节值得说。第一经纬度千万别用FLOAT。FLOAT是单精度存到小数点后六七位时会出现精度漂移比如116.397428存进去读出来变成116.397430做距离计算时误差会被放大。用DECIMAL(10,6)或者DOUBLE都行前者更稳。第二id如果沿用官方六位区划代码好处是和外部系统对接时不用做映射坏处是区划调整撤县设区之类后代码会变需要定期更新数据版本。2.2 层级关系与索引设计自关联表最常见的查询是「给一个区县往上找市和省」以及「给一个省往下找所有市」。前者靠parent_id逐级查后者靠parent_id反查子节点。所以parent_id上必须建索引level上也建议建一个因为按层级筛选比如只取所有地级市是高频操作。-- 建表时就把索引规划好别等数据导完再补 CREATE TABLE region ( id INT NOT NULL COMMENT 行政区划代码, parent_id INT NOT NULL DEFAULT 0 COMMENT 上级区划id省级为0, name VARCHAR(64) NOT NULL COMMENT 区划名称, level TINYINT NOT NULL COMMENT 1省 2市 3区县 4乡镇, lng DECIMAL(10,6) DEFAULT NULL COMMENT 经度, lat DECIMAL(10,6) DEFAULT NULL COMMENT 纬度, pinyin VARCHAR(64) DEFAULT NULL COMMENT 拼音, PRIMARY KEY (id), KEY idx_parent (parent_id), KEY idx_level (level), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT全国行政区划经纬度;建表语句里的utf8mb4不是可选项。区划名称里偶尔会出现生僻字utf8三字节编码存不下导入时会直接报Incorrect string value这个坑后面还会细说。idx_name是为模糊搜索准备的如果数据量到乡镇级四万多行LIKE %xx%走不了索引但前缀匹配LIKE xx%能走做输入联想时尽量用前缀。2.3 坐标精度与坐标系问题这是最容易被忽略的一点数据里的经纬度到底是哪种坐标系国内常见的有 WGS84GPS 原始坐标、GCJ-02俗称火星坐标高德、腾讯地图用、BD-09百度地图用。三者之间差几百米直接混用会导致地图打点偏移。这份数据如果没标注坐标系导入前先拿几个已知地点比如某市中心和地图上对比一下偏差在几十米内大概率是 GCJ-02偏差几百米且方向固定多半是 WGS84 没纠偏。选型上如果前端用高德或腾讯地图数据是 WGS84 的话得在展示层做一次转换如果数据本身就是 GCJ-02直接存直接用省事。3. 导入实战从 SQL 文件到可查询的表数据到手通常是.sql文件或者.csv。.sql直接source最省事.csv则要过一遍LOAD DATA。两种方式各有各的坑分开说。3.1 SQL 文件导入与字符集设置命令行导入前先确认客户端和服务端的字符集一致否则中文区划名会变问号。# 登录时显式指定字符集避免中文乱码 mysql -u root -p --default-character-setutf8mb4 your_db # 进入 mysql 客户端后执行 SET NAMES utf8mb4; SOURCE /path/to/region.sql;SET NAMES utf8mb4这行不能省。它告诉服务端「我发过来的字节按 utf8mb4 解释」如果客户端默认是 latin1 或 utf8导入过程中中文就会损坏而且这种损坏是不可逆的——存进去是问号后面怎么改字符集都救不回来。导入完成后用SELECT COUNT(*)核对行数再SELECT * FROM region WHERE level1看看省级名称是否正常显示两步都过了才算导入成功。3.2 CSV 导入与字段映射如果拿到的是 CSV用LOAD DATA INFILE比逐行INSERT快一个数量级。但要注意secure_file_priv限制和字段分隔符。-- 先看服务端允许从哪个目录读文件 SHOW VARIABLES LIKE secure_file_priv; -- 假设文件放在允许目录下字段以逗号分隔首行为表头 LOAD DATA INFILE /var/lib/mysql-files/region.csv INTO TABLE region CHARACTER SET utf8mb4 FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (id, parent_id, name, level, lng, lat, pinyin);IGNORE 1 LINES跳过表头OPTIONALLY ENCLOSED BY 处理名称里带逗号的情况虽然区划名一般不带但 CSV 导出工具常给所有字段加引号。如果secure_file_priv是NULL说明服务端禁用了文件导入得改成指定目录或者用客户端工具导入。导入后同样要抽查随机取几条区县记录看lng、lat是否为空空值多说明源数据本身有缺失得决定是补还是过滤。3.3 导入后的完整性校验数据导进去不等于能用。我一般会跑三条校验 SQL一是层级分布是否合理省级约 34、地级约 330、区县约 2800数量级不对说明数据不全二是parent_id是否存在孤儿节点子节点的父 id 在表里找不到三是经纬度是否落在合理范围国内大致经度 73~135、纬度 3~54。-- 层级分布 SELECT level, COUNT(*) FROM region GROUP BY level; -- 孤儿节点parent_id 非0但父节点不存在 SELECT c.id, c.name, c.parent_id FROM region c LEFT JOIN region p ON c.parent_id p.id WHERE c.parent_id 0 AND p.id IS NULL; -- 坐标越界 SELECT id, name, lng, lat FROM region WHERE lng NOT BETWEEN 73 AND 135 OR lat NOT BETWEEN 3 AND 54;孤儿节点查询用LEFT JOIN ... IS NULL比NOT IN快后者在子查询结果集大时性能会崩。坐标越界那几条如果查出来多半是源数据把经纬度写反了经度写成纬度这种错误在地图上一眼能看出来但纯看数字不容易发现所以校验 SQL 值得跑一遍。4. 查询与业务落地把坐标用起来的几种姿势数据躺在表里没意义关键是接到业务里。这一章讲三个高频用法按名称查坐标、按坐标反查行政区、按距离筛选。每个都给出可抄的 SQL 和参数说明。4.1 按名称模糊查坐标用户输入「某区」要拿到它的经纬度。最直接的是LIKE但要注意匹配策略。-- 精确匹配优先前缀匹配兜底 SELECT id, name, level, lng, lat FROM region WHERE name 某区 OR name LIKE 某区% ORDER BY level DESC LIMIT 10;ORDER BY level DESC是为了让层级更细的结果排前面——用户搜「某区」可能同时匹配到市辖区和某个同名乡镇区县级level3比乡镇级level4更可能是目标。LIMIT 10防止模糊匹配返回太多。如果数据量到乡镇级LIKE %某区%这种前后都带通配符的写法会全表扫描四万多行还能扛再大就得考虑上全文索引或者拼音首字母辅助匹配了。4.2 按坐标反查行政区点在多边形内严格来说光有中心点坐标做不了精确的「点在哪个区」那需要行政区边界多边形数据。但如果业务只要求「离哪个区中心最近」用球面距离排序就够了。-- 给定坐标找最近的区县中心Haversine 公式近似 SELECT id, name, ROUND(6371 * ACOS( COS(RADIANS(30.5)) * COS(RADIANS(lat)) * COS(RADIANS(lng) - RADIANS(120.1)) SIN(RADIANS(30.5)) * SIN(RADIANS(lat)) ), 2) AS distance_km FROM region WHERE level 3 ORDER BY distance_km LIMIT 5;6371是地球平均半径公里RADIANS把角度转弧度。这个公式在短距离内误差很小做「最近区县」够用。注意WHERE level 3限定只算区县否则乡镇中心点更密结果会偏向乡镇。如果业务要求精确的行政归属那得另找边界数据中心点坐标只能做近似这点要跟产品说清楚别背锅。4.3 按距离筛选门店或网点反过来给一个用户坐标筛出半径 10 公里内的门店门店表里存了区县 id通过区县中心坐标做粗筛。-- 先用经纬度矩形框粗筛再用 Haversine 精算 SELECT s.id, s.name, r.name AS region_name, ROUND(6371 * ACOS( COS(RADIANS(30.5)) * COS(RADIANS(r.lat)) * COS(RADIANS(r.lng) - RADIANS(120.1)) SIN(RADIANS(30.5)) * SIN(RADIANS(r.lat)) ), 2) AS distance_km FROM store s JOIN region r ON s.region_id r.id WHERE r.lat BETWEEN 30.5 - 0.1 AND 30.5 0.1 AND r.lng BETWEEN 120.1 - 0.12 AND 120.1 0.12 HAVING distance_km 10 ORDER BY distance_km;BETWEEN那个矩形框是粗筛0.1 度纬度约 11 公里0.12 度经度在中纬度约 11 公里框略大于目标半径保证不漏。粗筛能走lat、lng上的索引如果建了的话把候选集压小之后再算 Haversine比全表算距离快得多。这是空间查询的常规套路MySQL 8.0 有ST_Distance_Sphere函数可以更简洁但老版本还得手写公式。5. 避坑与排查导入和使用中最容易翻车的五件事5.1 中文区划名变问号或乱码现象导入后SELECT出来区划名全是???或者锟斤拷。原因客户端、连接、表三者字符集不一致最常见是客户端默认 latin1 而表是 utf8mb4。解决导入前SET NAMES utf8mb4建表用utf8mb4连接串加characterEncodingutf8。已经损坏的数据没法原地修复只能删表重导所以这一步要在导入前做对。5.2 经纬度精度丢失导致地图偏移现象地图打点偏了几十米到几百米。原因字段用了FLOAT或者导入时 CSV 被 Excel 打开过、自动截断了小数位。解决字段改DECIMAL(10,6)CSV 用文本编辑器或命令行处理别用 Excel 另存。已经导入的用ALTER TABLE改字段类型后重导数据。5.3 区划代码变更导致关联断裂现象门店表里存的区县 id 在 region 表里查不到。原因行政区划调整撤县设区、合并后代码变了旧数据没同步。解决region 表保留历史版本或者加is_active标记关联查询用LEFT JOIN并处理 NULL别用INNER JOIN把数据悄悄过滤掉。定期比对官方区划代码更新数据。5.4 坐标系混用导致整体偏移现象所有点在地图上朝同一个方向偏几百米。原因数据是 WGS84前端用 GCJ-02 地图渲染没做纠偏。解决确认数据坐标系在展示层统一转换或者导入前批量转成目标坐标系。别在不同环节混着用偏移是系统性的不是随机误差。5.5 乡镇级数据缺失或重复现象按区县查下属乡镇数量对不上或者有重复。原因源数据乡镇级不全或者导入时主键冲突被REPLACE覆盖。解决导入前SELECT id, COUNT(*) ... GROUP BY id HAVING COUNT(*)1查重导入用INSERT IGNORE而非REPLACE缺失的乡镇按需补别假设数据一定完整。6. 进阶技巧用递归 CTE 一次查出完整地址链实际业务里经常需要把「省市区」拼成完整地址或者从区县 id 一路往上拿到省。MySQL 8.0 之前只能写循环或者多次查询8.0 之后递归 CTE 能一条 SQL 搞定。这个技巧值得单独拎出来因为地址链拼接是这类数据最高频的衍生需求。-- 从任意区县 id 往上递归拼出「省/市/区」完整路径 WITH RECURSIVE addr AS ( SELECT id, parent_id, name, level, 1 AS depth FROM region WHERE id 330106 -- 起始区县 id UNION ALL SELECT p.id, p.parent_id, p.name, p.level, c.depth 1 FROM region p JOIN addr c ON p.id c.parent_id WHERE c.parent_id 0 ) SELECT GROUP_CONCAT(name ORDER BY depth DESC SEPARATOR /) AS full_path FROM addr;WITH RECURSIVE定义递归 CTE锚点部分是起始区县递归部分沿parent_id往上找父节点depth记录层级用于最后排序。GROUP_CONCAT按depth DESC排保证输出是「省/市/区」而不是反过来。WHERE c.parent_id 0是递归终止条件到省级parent_id0就停不加这行会无限递归直到报错。这个写法比在应用层写 while 循环干净得多而且一次网络往返就拿到结果。反过来从省往下展开整棵子树也一样-- 给定省 id递归查出其下所有市和区县 WITH RECURSIVE sub AS ( SELECT id, parent_id, name, level FROM region WHERE id 330000 UNION ALL SELECT r.id, r.parent_id, r.name, r.level FROM region r JOIN sub s ON r.parent_id s.id ) SELECT * FROM sub WHERE level 2 ORDER BY level, id;递归方向从「找父」变成「找子」JOIN条件反过来写r.parent_id s.id。这两个模板基本覆盖了地址链的所有查询场景改一下起始 id 和终止条件就能复用。最后说个验证习惯。这类坐标数据导入后我每次都会挑三五个自己熟悉的地点把经纬度丢到地图上肉眼核对一遍再看偏差方向和量级——偏几十米是坐标系问题偏几百米是精度或写反了偏到另一个城市那就是数据本身错了。这个动作花不了两分钟但能挡掉后面一堆「地图打点不准」的返工。从那以后我每次导完坐标数据都强制走一遍这个肉眼校验比任何自动化脚本都直观。希望帮到你。本文还有配套的精品资源点击获取