☰
MySQL导入6000+旅游城市SQL数据:字符集、校验与避坑指南
2026/9/26 14:32:07 网站建设 项目流程

简介:面向数据分析师、MySQL开发者及餐饮旅游行业从业者,这份资源将全球旅游城市核心数据整理为可直接运行的SQL脚本,帮助快速搭建城市与餐饮信息查询环境,省去手工建表与数据录入环节。压缩包内仅含1个sql文件,整体约133KB,文件虽小但数据密度高,导入MySQL后即可基于6000余条记录开展统计分析。目前已有317人学习下载。SQL语句覆盖城市名称、地理位置、人口、著名景点与餐饮业信息等多维字段,配合索引优化与常用查询语法,可高效得出热门旅游城市、餐饮聚集区域等结论;同时也适合作为数据库导入导出、备份恢复及数据清洗操作的练习素材。资源结构简洁,便于二次开发时整合进旅游推荐或餐厅预订类Web应用,对理解关系型数据库设计也有一定参考价值。

1. 拿到的这份 SQL 能做什么:6000+ 条旅游城市数据的真实价值

把压缩包解压之后,里面通常就是一个 travel_area.sql,而不是一堆散落的 CSV。这份全球旅游城市数据按可直接执行的 MySQL SQL 语句打包,导入即查,比从零建表省事得多。但「能导入」和「能用好」是两码事——我拆过不少这类外来数据包,最常见的结果是字符集乱码、导入只成功一半、查询时 COUNT 对不上账。这份资源核心能解决三件事:一是给餐饮旅游业务做趋势分析和区域对比时,有一份现成的城市维度基础数据;二是做报表原型、课程设计、测试环境时不用再为造数据发愁;三是练手 SQL 聚合和可视化时,6000+ 条记录量级刚刚好,既有统计意义,又不会慢到让人放弃。适合数据分析、后端开发、餐饮旅游行业选型,以及需要真实数据集的课程设计。

2. 看懂 travel_area.sql 的表结构:字段、关系与数据边界

拿到外部 dump 的第一反应不应该是直接导入,而是先看结构。不同来源的 SQL 文件字段命名差异很大,有的叫 city_name,有的叫 name,有的把景点和餐饮信息拆成三张表,有的全部塞在一张表里用逗号分隔。先花两分钟确认结构,能避免后续所有查询脚本白写。

2.1 字段字典与表关系:这份数据包到底塞了什么

看结构最直接的方式是先打开文件头,不急着连数据库:

head -n 80 travel_area.sql

这段命令会输出 SQL 文件最前面的 80 行,里面通常包含建库语句、USE 语句和 CREATE TABLE 定义。重点看三件事:目标库名是什么、表名是什么、字段列表长什么样。如果文件里带了CREATE DATABASE travel_db和USE travel_db,导入时会自动建库并切换,不需要手动干预。

按这份数据包常见的形态来看,主表字段大致如下,实际以你解压后SHOW COLUMNS的结果为准:

字段名常见类型说明
city_idINT / BIGINT城市主键,自增
city_nameVARCHAR(100)城市名称,可能是英文或中文
countryVARCHAR(100)所属国家或地区
regionVARCHAR(100)州 / 省 / 大区,部分记录为空
latitudeDECIMAL(10,6)纬度,范围 -90 到 90
longitudeDECIMAL(10,6)经度,范围 -180 到 180
populationINT / BIGINT城市人口,部分记录为 0 或 NULL
famous_attractionsVARCHAR/TEXT著名景点,可能多条以逗号分隔
restaurant_countINT餐饮商户数量,部分记录为空
categoryVARCHAR(50)城市类型或标签,如海滨、文化古城
insert_timeDATETIME数据写入时间

字段之间大多是平铺关系,不是严格意义上的多表设计。景点字段用逗号分隔或 JSON 文本存放,是这类数据包的常见做法——优点是导出方便、导入简单,缺点是做景点维度的精细分析时要自己做拆分。

先确认表结构,再决定怎么用:

SHOW TABLES;
SELECT TABLE_NAME, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'travel_db';

第一句列出库里的所有表;第二句从 information_schema 里读每张表的估算行数。注意TABLE_ROWS是引擎估算值,不一定精确,最终以COUNT(*)为准。如果这里出现多张表,通常意味着数据被拆成了城市、景点、餐饮三类,关系键多半是 city_id。

2.2 6000+ 行数据的分布:地理范围、填充率与数据边界

结构确认之后,要看数据长什么样。最容易上手的是按国家或地区分组,看数据覆盖了哪些地方、分布是否均匀:

SELECT country, COUNT(*) AS city_cnt FROM travel_area GROUP BY country ORDER BY city_cnt DESC LIMIT 15;

这条 SQL 按 country 分组统计每个国家的城市记录数,再按数量倒序取前 15 名。跑完你会发现数据集中在热门旅游目的地,例如日本、意大利、法国、泰国这些国家记录数偏多,而一些小众国家可能只有一两条。这对后面做分析很重要——如果做「全球餐饮分布对比」,小众国家样本太少,结论容易失真,分析时要单独标注或过滤。

再看一下单条记录的完整度:

SELECT city_name, country, population, famous_attractions, restaurant_count FROM travel_area ORDER BY RAND() LIMIT 10;

ORDER BY RAND()会随机抽 10 条,用来快速感知数据的真实面貌。我一般会重点看 population 和 restaurant_count 这两个数值字段:如果大量记录是 0 或 NULL,说明这份数据更适合做城市名录和地理分析,不太适合做精确的餐饮营收对比。

还有一点容易被忽略——有些记录可能不是城市,而是景区、岛屿或地区。抽样时要注意 city_name 里是否混入「Bali」这类岛屿名或「Provence」这类地区名。城市粒度决定后续分析的精度:如果要做经纬度半径检索,市级坐标和区县级坐标的误差范围完全不同。这份数据的合理边界也在这里——适合做城市维度的趋势分析、教学演示、报表原型,不适合直接当生产系统的唯一数据源,拿来之前必须做一轮质量校验。

3. 把数据灌进 MySQL:命令行、图形化与字符集三关

导入这一步,新手卡住的概率最高。SQL 文件本身没有错,但客户端字符集、文件编码、MySQL 服务状态任何一个不对,都会让导入失败或者数据变乱码。先把最简单可靠的路径走通,再谈图形化工具。

3.1 命令行导入:最快的一条路

命令行是处理 SQL dump 最可靠的方式,没有图形化界面的干扰,报错也最直接:

mysql -uroot -p --default-character-set=utf8mb4 < travel_area.sql

参数说明:-u指定用户名,这里为 root;-p让命令行交互式询问密码;--default-character-set=utf8mb4告诉客户端按 utf8mb4 编码解释文件内容;<是 shell 重定向,把文件内容作为 mysql 客户端的标准输入。执行后终端会有提示输入密码,输入正确后没有任何输出通常意味着导入成功。

为什么不建议直接写mysql -uroot -p < travel_area.sql不加字符集参数?因为 MySQL 客户端的默认字符集可能和文件的实际编码不一致。如果文件里包含中文城市名,而客户端按 latin1 或 utf8 解释,轻则中文变问号,重则直接报ERROR 1366 (HY000): Incorrect string value。

导入前先确认文件头部是否带了建库语句:

grep -E "CREATE DATABASE|USE " travel_area.sql | head -n 5

这个命令把文件里的建库和切库语句过滤出来。如果输出里有CREATE DATABASE travel_db和USE travel_db,导入后数据库会自动建好,后续查询指定库名即可。如果没有,说明文件里只有建表和插入语句,需要手动先建库,再指定库导入:

mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS travel_db DEFAULT CHARACTER SET utf8mb4;" mysql -uroot -p --default-character-set=utf8mb4 travel_db < travel_area.sql

第一条命令创建一个默认字符集为 utf8mb4 的空库;第二条把 SQL 文件导入到该库中。这两条组合适用于文件里没有 USE 语句的情况,也是最不容易踩坑的导入姿势。

导入完成后立刻验证:

USE travel_db; SELECT COUNT(*) FROM travel_area;

如果行数和源文件描述的数量一致(本资源为 6000+),说明导入完整;如果少了几百行,大概率是导入过程中遇到了错误但客户端没停下来,需要看后面的避坑章节逐条排查。

3.2 图形化工具导入:Workbench 与 Navicat 的差异

图形化工具适合喜欢看进度条和界面的场景,但要注意行为差异。MySQL Workbench 的导入路径是 Server → Data Import → Import from Self-Contained File,选择 travel_area.sql 后点击 Start Import。关键点是 Default Schema 这一项:如果不选,Workbench 会根据文件里的 USE 语句自动落库;如果选了,文件里的 USE 语句可能被忽略,导致数据进到你指定的库里,这个细节容易让人误以为导入失败。

Navicat 的操作路径是右键目标数据库 → 运行 SQL 文件 → 选择文件 → 运行。Navicat 默认会逐条执行文件里的语句,遇到报错时弹窗提示但可以继续往下跑。所以这里有个矛盾点:继续跑能保证大部分数据进去,但中间跳过的语句会造成数据缺失而不自知。我一般建议第一次导入时不勾选「遇到错误继续」,让它在第一个错误处停下来,把问题暴露在明面。

图形化工具和命令行导入的底层机制其实是同一套:客户端把 SQL 文件的内容发送给 MySQL 服务器逐条执行。差异在于客户端默认字符集的处理方式不同——Workbench 的字符集默认跟随连接配置,Navicat 多数情况下能自动识别 UTF-8 文件,但遇到 GBK 编码的文件同样会乱码。所以不管是哪条路径,绕不开的都是先确认源文件编码,再让客户端和文件保持一致的字符集。

4. 数据质量先过一遍:字符集、去重与坐标校验的落地脚本

这一步是「能查」和「能放心用」之间的分水岭。外部数据包来源不明,字段填充率、重复记录、坐标越界都是常态。不校验就去做报表,最后交付的分析结论随时可能被一条脏数据推翻。

4.1 字符集与乱码体检:先确认编码再谈分析

导入完成后的第一件事,不是写复杂查询,而是检查中文到底有没有乱码。先看文件本身的编码格式:

file -I travel_area.sql

file命令会输出文件的 MIME 类型和编码信息,例如charset=utf-8表示 UTF-8 编码。如果输出显示charset=iso-8859-1或charset=unknown,说明文件可能不是 UTF-8,导入时乱码的风险极高。

文件编码正确不代表导入就没问题,还要在库里验证一遍:

SELECT COUNT(*) AS non_ascii_cnt FROM travel_area WHERE LENGTH(city_name) <> CHAR_LENGTH(city_name);

这条 SQL 利用了 MySQL 中LENGTH()返回字节数、CHAR_LENGTH()返回字符数的差异:如果 city_name 里有中文,字节数必然大于字符数,两者不等的记录数就是非纯 ASCII 的城市名数量。如果这个数字接近 0,说明数据里根本没有中文,自然不会乱码;如果数字很大,说明确有中文内容,需要抽查是否显示正常。

抽查具体内容:

SELECT city_name, country FROM travel_area WHERE LENGTH(city_name) <> CHAR_LENGTH(city_name) LIMIT 10;

如果查询结果里的中文显示为???或æ±äº¬这类符号,说明字符集在导入时已经出了问题。最稳妥的解决路径是清空表后重新导入,重点确认客户端的--default-character-set=utf8mb4参数没写错。表结构定义里的字符集也可以在导入前统一改掉,常见做法是编辑 SQL 文件,把DEFAULT CHARSET=utf8全局替换成DEFAULT CHARSET=utf8mb4:

sed -i 's/DEFAULT CHARSET=utf8/DEFAULT CHARSET=utf8mb4/g' travel_area.sql

这个 sed 全局替换会把建表语句里的字符集声明改成 utf8mb4。注意替换前先备份原文件,因为 sed -i 是直接修改原文件,没有后悔药。

4.2 去重、空值与坐标校验:三组 SQL 解决大部分脏数据

字符集之外,数据质量检查集中在三类问题:重复记录、空值、坐标越界。

先查重复:

SELECT city_name, country, COUNT(*) AS dup_cnt FROM travel_area GROUP BY city_name, country HAVING dup_cnt > 1 ORDER BY dup_cnt DESC;

这里的去重逻辑用了 city_name + country 联合判断,而不是只按 city_name。原因很简单:同名城市在不同国家大量存在,比如 San Jose 在美国和哥斯达黎加都有,只按城市名分组会把正常记录误判成重复。联合分组才能把真正意义上的重复抓出来。

再统计空值分布:

SELECT SUM(population IS NULL OR population = 0) AS no_population, SUM(restaurant_count IS NULL) AS no_restaurant, SUM(famous_attractions IS NULL OR famous_attractions = '') AS no_attractions FROM travel_area;

这条 SQL 用 SUM 配合条件判断,统计三个关键字段的缺失情况。注意我特意把 population 的「NULL」和「显式为 0」放在一起统计,因为人口为 0 和没有人口数据,在分析视角下都表示「这个字段不可用」。但 restaurant_count 只统计了 NULL,因为 0 家餐厅本身是有意义的业务事实,不能当作缺失值处理。如果你在分析中需要区分「没数据」和「数据为 0」,这里的统计逻辑要做相应拆分。

坐标校验:

SELECT COUNT(*) AS bad_latlng FROM travel_area WHERE latitude NOT BETWEEN -90 AND 90 OR longitude NOT BETWEEN -180 AND 180;

纬度的合法范围是 [-90, 90],经度是 [-180, 180],这个边界来自地理坐标系的定义,不是拍脑袋定的。超出这个范围的记录要么是坐标写错,要么是经纬度字段装反了,分析时如果要做距离计算,这些记录会直接污染结果。

把校验通过的记录做成视图,后续查询只碰干净数据:

CREATE OR REPLACE VIEW v_city_clean AS SELECT city_id, city_name, country, region, population, restaurant_count FROM travel_area WHERE latitude BETWEEN -90 AND 90 AND longitude BETWEEN -180 AND 180 AND (famous_attractions IS NOT NULL AND famous_attractions <> '');

视图的好处是不改动原表,只把符合条件的记录暴露给下游。后续所有分析查询都从v_city_clean取数,可以避免每条 SQL 都重复写一遍过滤条件。视图本身只是逻辑映射,不占额外存储,数据量大时性能取决于原表的索引情况。

5. 常见问题与避坑手册:从导入到查询的七处高频翻车点

这部分是血泪经验汇总。拆过不少外来数据包之后,发现翻车的点位高度集中,提前知道能省下大量排查时间。

5.1 导入阶段:报错、卡死与半截导入

坑 1:中文变乱码,显示为 ??? 或拼音符号

现象:导入后查询中文城市名,显示为???或类似æ±äº¬的乱码符号。

原因:SQL 文件本身的编码不是 UTF-8,但客户端按 utf8mb4 解释;或者客户端字符集和服务器不一致,导致存储时字节被错误截断。UTF-8 编码的中文三字节,latin1 解释下每字节变成一个单独字符,就出现了花式乱码。

解决:先执行file -I travel_area.sql确认真实编码。如果文件是 GBK,用下面的命令转成 UTF-8 再导:

iconv -f GBK -t UTF-8 travel_area.sql > travel_area_utf8.sql mysql -uroot -p --default-character-set=utf8mb4 < travel_area_utf8.sql

转换前提是文件里没有超出 GBK 字符集的字符,例如 emoji 和部分生僻字,否则 iconv 会报错。遇到时报错时改用-c参数忽略无法转换的字符,但要想清楚这会不会丢数据。

坑 2:导入时报 ERROR 1064 语法错误

现象:导入过程在某个 INSERT 语句处报ERROR 1064 (42000): You have an error in your SQL syntax,导入中断或跳过该段数据。

原因:SQL 文件在 Windows 下被编辑过,行尾带 CRLF 换行或文件开头带 BOM 头,MySQL 解析器遇到这些非法字符后报语法错误。尤其是用记事本打开并保存过的 SQL 文件,几乎必踩这个坑。

解决:导入前用 sed 清理行尾和控制字符:

sed -i 's/\r$//' travel_area.sql sed -i 's/^\xEF\xBB\xBF//' travel_area.sql

第一条把行尾的 CR 去掉,第二条把 UTF-8 BOM 头去掉。注意 sed -i 直接改原文件,先备份再执行。

坑 3:导入到一半断掉,重导报 Duplicate entry 主键冲突

现象:第一次导入网络或终端中断,第二次重导时提示ERROR 1062 Duplicate entry '123' for key 'PRIMARY'。

原因:第一次导入已经写入了一部分数据,第二次导入又从头开始执行 INSERT,主键冲突。MySQL 的 dump 默认不会在 INSERT 前清空已有数据。

解决:确认表里已有数据不再需要时,先删除再重导:

TRUNCATE TABLE travel_area;

TRUNCATE会清空表并重置自增主键,比DELETE FROM快,但不可回滚。如果表里有外键关联,TRUNCATE会失败,需要先处理关联关系。更稳妥的做法是重导前先SHOW COUNT(*)确认现有行数,再决定是否清空。

坑 4:mysql 命令连本地报 ERROR 2002 socket 连接失败

现象:执行mysql -uroot -p时报ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'。

原因:MySQL 服务没有启动,或者客户端找 socket 文件的路径和服务端实际路径不一致。常见于刚装完 MySQL、服务还没拉起的环境。

解决:先启动服务:

systemctl start mysqld

服务起来后 socket 文件会重新生成。如果启动正常仍报错,说明 socket 路径不对,改用 TCP 方式连接:

mysql -uroot -p -h 127.0.0.1 -P 3306

-h 127.0.0.1强制走 TCP 而不是 socket,-P 3306指定端口。这两种路径本质都连同一个 MySQL 实例,只是通信方式不同,socket 比 TCP 快一点,但 TCP 更通用。

5.2 查询与分析阶段:数据对不上与查询不够快

坑 5:COUNT(*) 和预估的行数对不上

现象:导入完跑SELECT COUNT(*),得到的行数和导入前期望的 6000+ 差了几百条。

原因:导入过程中有语句报错被跳过,但错误没有被注意到。尤其是图形化工具导入时勾选了「遇到错误继续」,会让失败语句静默跳过。

解决:导入后立即做行数对账:

mysql -uroot -p --default-character-set=utf8mb4 -e " SELECT COUNT(*) FROM travel_db.travel_area;"

如果对不上,最可靠的做法是重导一遍:确认数据库里没有其他重要数据时,删除该表并重新导入。不要试图手动补几条记录,因为你不知道具体缺了哪几条。从那以后我每次都把导入前后的 COUNT 结果截图留存,作为对账依据。

坑 6:只有几千条数据,查询却感觉不够快

现象:针对 country 字段做 WHERE 过滤,量级只有几千条,但响应时间不稳定。

原因:数据量确实不大,但表里没有针对 country 和 city_name 建索引。每次查询都是全表扫描,遇到多表 JOIN 时更明显。几千条数据不会慢到不可接受,但这是坏习惯的开端——数据量涨到几十万条时,同样的查询方式会直接拖垮分析任务。

解决:给常用过滤字段加上普通索引:

CREATE INDEX idx_country ON travel_area(country); CREATE INDEX idx_city_name ON travel_area(city_name);

第一条加在 country 上,适合按国家筛选的业务场景;第二条加在城市名上,适合按名称查询的场景。索引会占用额外存储空间,但对这种量级的表,开销可以忽略不计。写分析 SQL 时,可以配合EXPLAIN看执行计划:

EXPLAIN SELECT * FROM travel_area WHERE country = 'Japan';

重点看 type 列,如果是ALL说明是全表扫描,加上索引后应该变成ref或range。这一步能直观验证索引是否生效。

坑 7:导出 CSV 给 Excel 打开,中文一片乱码

现象:用 SQL 结果导出 CSV 后,Excel 直接打开,中文全部乱码,英文正常。

原因:MySQL 导出的 CSV 默认是 UTF-8 编码,而 Windows 版 Excel 打开 CSV 时默认按 ANSI(GBK)解码。UTF-8 的中文字节被按 GBK 解释,自然乱码。

解决:导出时加 UTF-8 BOM 头,Excel 就能正确识别编码。Python 一行搞定:

with open('travel_city.csv', encoding='utf-8') as f: content = f.read() with open('travel_city_bom.csv', 'w', encoding='utf-8-sig') as f: f.write(content)

utf-8-sig就是带 BOM 的 UTF-8,写入文件时会自动加上 BOM 头。Excel 看到 BOM 会按 UTF-8 解码,中文不再乱码。注意这个操作只影响导出文件,不改数据表本身。

6. 备份、导出与最小闭环:把这份数据喂给报表和 Web 应用

数据校验完,就到真正出活的时候。备份这部分建议在使用前做而不是使用后,因为分析过程中可能会有误操作,改坏了数据再后悔就迟了。

6.1 用 mysqldump 做一次干净的备份

mysqldump -uroot -p --default-character-set=utf8mb4 --single-transaction travel_db > travel_db_backup.sql

--single-transaction对 InnoDB 表做一致性快照,备份过程中不锁表,业务查询不受影响;--default-character-set=utf8mb4保证导出文件的编码和原库一致。备份文件就是一份可随时恢复的 SQL dump,把这文件收好,等于给数据上了后悔药。

恢复时直接执行:

mysql -uroot -p --default-character-set=utf8mb4 < travel_db_backup.sql

和第一次导入一样的姿势。备份文件命名时带上日期更靠谱,比如travel_db_backup_20250101.sql,不然备份了一大堆分不清哪个是最新的。

6.2 从 SQL 到报表:导出 CSV 的最小闭环

把干净视图导出成 CSV 给报表工具用:

SELECT city_name, country, population, restaurant_count INTO OUTFILE '/tmp/travel_city.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' FROM v_city_clean;

INTO OUTFILE会把结果直接写到服务器本地文件,FIELDS TERMINATED BY ','指定逗号分隔,OPTIONALLY ENCLOSED BY '"'给字符串字段加双引号,防止字段内部有逗号导致列错位。注意三点:文件只能写到服务器本地,不能指定任意远程路径;MySQL 的secure_file_priv参数会限制输出目录,如果报错就查这个变量;文件路径要确保 MySQL 进程有写权限。

如果服务器上不方便操作,也可以用命令行客户端配合重定向导出:

mysql -uroot -p --default-character-set=utf8mb4 -e " SELECT city_name, country, population, restaurant_count FROM travel_db.v_city_clean;" > /tmp/travel_city.csv

这段命令把查询结果重定向到本地文件,配合--default-character-set=utf8mb4能保证输出编码正确。这种方式不依赖INTO OUTFILE,也不用担心 secure_file_priv 的限制,适合快速导出。

如果这份数据要做 Web 应用的数据源,我给个最简路线:给查询字段建好索引,建一个只读账号避免误操作,然后用一个 RESTful 接口把v_city_clean暴露出去。需求不复杂时,Python Flask 配 MySQL 连接池百来行就能搞定,不需要上重型框架。

那次给业务方交付城市分布报表,因为没在源文件环节确认编码,导出 CSV 让 Excel 开出一片乱码,连夜重导还差点把原始数据覆盖掉。从那以后我每次拿到外部 dump,都强制走一遍文件编码确认、导入后 COUNT 对账、抽查三条记录这套流程,再谈分析。这份 travel_area.sql 也一样:先照第 2 章的表结构核对字段,按第 5 章的坑位逐个趟,6000 多行数据十几分钟就能用起来。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询