ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL零基础实战:从环境搭建到SQL优化与面试通关指南

MySQL零基础实战:从环境搭建到SQL优化与面试通关指南 SQL 这玩意儿说难也难说简单也简单。难的是网上教程千篇一律装个 MySQL 就能把新手卡死在第一步简单的是只要环境起来、语句上手、面试题心里有数后面基本就是水到渠成的事。我做了这么多年开发和数据相关工作带过的实习生、转行过来的朋友多多少少都踩过同样的坑环境装不上、SQL 记不住、面到数据库题脑子一片空白。这篇博文就围绕这三个痛点展开——MySQL 环境一键搭建、SQL 基础语句完整梳理、面试习题逐题拆解目标读者是零基础想入门数据分析、后端开发或者正在准备面试的朋友。内容不装高深全是我实操验证过的路径照着走就行。1. 环境搭建MySQL 装不上后面全是空谈1.1 版本怎么选新手直接上 8.0很多人一上来就卡在版本选择上MySQL 5.7 和 8.0 各说各的好。我的建议很简单新手直接装 8.0不用纠结。有个小知识点值得先说清楚你在网上会看到类似“mysql 5.7.44 官方为什么之后 5.7.43 呢”这样的搜索问题这其实是版本号看反了。版本号是按数字递增的5.7.43 之后是 5.7.445.7.44 是 5.7 系列的收官版本官方后续不再为 5.7 提供新的功能更新只做安全补丁维护。而 8.0 是长期维护的当前主流版本新特性都在 8.0 上比如窗口函数、通用表表达式WITH 语法、更好的性能优化器。哪怕你是为了应付老系统的维护去学 5.7我也建议先在 8.0 上把基础功练扎实两者在基础 SQL 语法上的差异不超过 5%学习成本几乎可以忽略。另外还有个环境选择的问题Windows 用户、Mac 用户、云服务器 Linux 用户都会问装哪个好。核心原则是你的工作环境在哪就在哪装。如果只是为了学习和练 SQLWindows 10/11 直接装本地版最快如果是为了模拟生产环境Linux 服务器更贴近真实部署场景。我从最简单的 Windows 环境讲起实操性最强。1.2 Windows 10 安装全流程实操从 zip 包到 net start mysql我在 Windows 上装 MySQL 8.0 用过 MSI 安装包也用过 zip 解压版强烈推荐 zip 解压版。理由很简单MSI 安装包附带一堆向导选项新手容易选错组件装完反而不知道东西在哪zip 版所有文件都装在一个目录里清晰可控卸载也方便直接删目录就行。具体步骤如下每一步都是验证过的第一步去 MySQL 官网下载mysql-8.0.x-winx64.zip。下载后解压到某个路径比如D:\mysql-8.0.46-winx64。注意路径里不要带中文、不要带空格否则后面配置容易出各种怪问题。第二步在解压目录下新建一个文本文件改名为my.ini填入以下内容[mysqld] # 端口默认3306 port3306 # 安装目录 basedirD:/mysql-8.0.46-winx64 # 数据存放目录 datadirD:/mysql-8.0.46-winx64/data # 字符集 character-set-serverutf8mb4 # 默认存储引擎 default-storage-engineINNODB [client] default-character-setutf8mb4这里有两个关键点。第一basedir和datadir建议写成反斜杠/不用\避免转义问题。第二不要手动创建 data 目录后续用命令初始化的时候 MySQL 会自动生成你手动建了反而可能因为目录权限或者内容冲突导致初始化失败。第三步以管理员身份打开命令提示符cmd进入 MySQL 解压目录的 bin 目录cd /d D:\mysql-8.0.46-winx64\bin先执行初始化命令mysqld --initialize-insecure这一步会在datadir目录生成系统数据库文件--initialize-insecure表示生成一个密码为空的 root 用户方便第一次登录。如果不用--initialize-insecure而是用--initializeMySQL 会生成一个随机密码写进日志文件里对新手来说找密码这一步很容易劝退所以还是用--initialize-insecure更省事。第四步安装系统服务并启动。继续在 bin 目录执行mysqld --install MySQL80 net start mysql注意一点mysqld --install后面的服务名是自定义的你可以叫 MySQL80也可以叫 mysql。但net start后面的名字必须和安装的服务名一致。网上很多教程里写的是net start mysql如果你安装时用了MySQL80那启动命令就要改成net start MySQL80否则系统会报“服务名无效”。这一步是新手报错频率最高的地方建议把服务名统一成 mysql 最简单安装命令直接写mysqld --install mysql。第五步登录 MySQL 并修改密码。启动成功后执行mysql -u root -p因为 root 密码为空提示输入密码时直接回车就能进入。进去后立刻执行修改密码命令ALTER USER rootlocalhost IDENTIFIED BY 123456;顺手设置一个远程登录账号按需CREATE USER admin% IDENTIFIED BY admin123; GRANT ALL PRIVILEGES ON *.* TO admin%; FLUSH PRIVILEGES;建议把bin目录添加到系统环境变量 PATH 里这样后续在任何路径执行mysql -u root -p都会直接生效不用每次都切目录。1.3 Linux 和 Docker 方案一条命令跑起来Linux 服务器上的安装以 CentOS/RHEL 系为例一般用 rpm 安装或者 yum 安装。rpm 的方式有些繁琐要先下载mysql-community-server相关 rpm 包再挨个安装新手容易遇到依赖缺失的问题。我更推荐在 Ubuntu/Debian 上用 apt 直接装简单省事sudo apt update sudo apt install mysql-server -y sudo systemctl start mysql sudo systemctl status mysql初始用户名是 root安装过程会提示设置密码。如果没提示可以用sudo mysql直接进入然后手动执行ALTER USER改密码。如果你不想污染本机环境或者需要快速起一个临时数据库练手Docker 是最合适的方案docker run -d \ --name mysql8 \ -e MYSQL_ROOT_PASSWORD123456 \ -p 3306:3306 \ mysql:8.0Docker 方式对配置文件的处理最干净不适合脚本化的多环境测试场景但对小白来说有个小门槛镜像拉取可能失败通常是网络源问题换一个国内可访问的镜像源即可--name指定的容器名不能和已有容器重复遇到“Conflict”报错时改个名字或者先docker rm mysql8再重跑。1.4 安装必坑清单服务名、端口、初始化第一次装 MySQL 的人十个里有八个会遇到下面几个问题端口被占用。启动时报3306端口被占用多半是之前装过 MySQL 残留服务或者装了其他占用 3306 的软件。先执行netstat -ano | findstr 3306看是哪个进程占用了端口再用服务管理器把旧的 MySQL 服务停掉或者直接换一个端口改my.ini里的port3307登录时用mysql -u root -P 3307 -p。data 目录无法初始化。这个坑也很常见因为my.ini里的datadir目录已经存在且不为空。解决方法是把data目录删掉或者换一个新路径重新执行mysqld --initialize-insecure。注意初始化成功后会生成一个data目录里面的auto.cnf文件是实例唯一标识不要随意删除。“mysqld 不是内部或外部命令”。这个问题简单就是没进入 bin 目录或者环境变量没配置直接cd到 bin 目录再执行。服务启动后秒退。查看 MySQL 的错误日志位置一般在datadir目录下的*.err文件里面会写明具体原因。绝大多数是路径配置错误、目录权限不足或者内存不足对着日志排查就行。2. SQL 基础语句从“照着敲”到“灵活写”2.1 先把名词理清楚SQL、MySQL、SQL Server 不是一回事很多新手会把 SQL、MySQL、SQL Server 混在一起这里先花半分钟理清三个词。SQL 是一种结构化查询语言是访问和操作关系型数据库的标准语言包含增删改查、建表、授权等操作。MySQL 是一个具体的关系型数据库管理系统它支持并实现了 SQL 标准同时扩展了自己的一些语法。SQL Server 则是微软家的数据库产品它同样支持 SQL 标准但细节语法和 MySQL 有差异比如分页用 TOP/OFFSET-FETCH而 MySQL 用 LIMIT。一句话总结SQL 是“话”MySQL 和 SQL Server 是“说这种话的人”学会 SQL 之后切换数据库产品的成本会低很多。2.2 DDL 与 DML建表、加字段、插入、更新、删除SQL 语句按功能分为四大类DDL数据定义语言、DML数据操作语言、DQL数据查询语言、DCL数据控制语言。新手最常接触的是 DDL 和 DML。DDL 里最常用的是建表语句。我拿一个用户表举例子CREATE DATABASE IF NOT EXISTS shop_db DEFAULT CHARSET utf8mb4; USE shop_db; CREATE TABLE user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键id, name VARCHAR(64) NOT NULL DEFAULT COMMENT 用户名, phone VARCHAR(20) NOT NULL DEFAULT COMMENT 手机号, age TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 年龄, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态0禁用1启用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;几个细节说一下AUTO_INCREMENT表示自增主键DEFAULT 0就是热门搜索里那种“MySQL 设置默认值为 0”的场景DATETIME DEFAULT CURRENT_TIMESTAMP写不写都行1 写跑不了字段名用反引号包起来可以避免和 MySQL 关键字冲突比如user索引KEY idx_phone在查询手机号时能加速这个后面索引章节细讲。DDL 里还有一个经常用到的操作是修改表结构比如加字段ALTER TABLE user ADD COLUMN nickname VARCHAR(64) NOT NULL DEFAULT COMMENT 昵称 AFTER name;DML 则是针对表中数据的操作。插入、更新、删除是三大金刚INSERT INTO user (name, phone, age) VALUES (小王, 13900001234, 25); UPDATE user SET age 26 WHERE id 1; DELETE FROM user WHERE id 1;这里必须强调UPDATE和DELETE一定要带WHERE条件否则会更新/删除全表数据。我真见过不止一个同事在测试环境顺手把整张表清空的情况。线上环境如果不确定条件是否精确建议先SELECT同样的WHERE条件查一遍再执行。2.3 DQL 查询核心SELECT、去重、排序、分页查询语句是 SQL 里使用频率最高的部分也是面试必考的核心。基础模板是SELECT 字段列表 FROM 表名 WHERE 过滤条件 GROUP BY 分组字段 HAVING 分组后过滤条件 ORDER BY 排序字段 LIMIT 分页偏移量, 每页数量;WHERE 条件里最常用的几个操作符包括、!、、、BETWEEN AND、IN、LIKE。比如查 22 到 30 岁之间的用户可以写成WHERE age BETWEEN 22 AND 30也可以写成WHERE age 22 AND age 30两种写法效果一样。LIKE做模糊查询时注意性能问题LIKE %str%的前置通配符会导致索引失效。DISTINCT 去重是热词里频繁出现的需求。比如查所有不重复的状态值SELECT DISTINCT status FROM user;还可以配合COUNT统计去重后的数量SELECT COUNT(DISTINCT phone) AS cnt FROM user;再去重时有一点要知道DISTINCT后面跟多个字段表示这些字段的组合值去重不是单独对每个字段去重。如果需要针对某一列去重但还要选出其他字段DISTINCT就不太够用了这时候需要窗口函数配合ROW_NUMBER()实现后面专门展开。ORDER BY 排序也很基础。比如按年龄倒序、创建时间正序排列SELECT id, name, age, create_time FROM user WHERE status 1 ORDER BY age DESC, create_time ASC;默认是ASC升序DESC是降序。多字段排序时从左到右依次生效先按age排年龄一样的按create_time排。LIMIT 分页是 MySQL 比 SQL Server 更直观的地方。每页 10 条第 3 页的数据就是从第 21 条开始SELECT * FROM user LIMIT 20, 10;第一参数是跳过多少条第二个参数是返回多少条。等价写法是LIMIT 10 OFFSET 20。注意OFFSET的写法在复杂 SQL 拼接时更容易读但原理一样。2.4 聚合与 JOIN从单表到多表的丝滑过渡聚合函数包括COUNT、SUM、AVG、MAX、MIN通常配合GROUP BY按组统计。比如按状态分组统计用户数和平均年龄SELECT status, COUNT(*) AS total_cnt, AVG(age) AS avg_age FROM user GROUP BY status;HAVING是针对分组后的数据进行二次过滤。比如筛选出平均年龄大于 25 的状态分组SELECT status, AVG(age) AS avg_age FROM user GROUP BY status HAVING avg_age 25;这里注意WHERE和HAVING的区别WHERE在分组前过滤原始行HAVING在分组后过滤聚合结果。能用WHERE过滤掉的不要放到HAVING因为先分组再过滤代价更高。多表 JOIN 是 SQL 小白到初级开发的必经之路。我用订单场景举例两张表orders订单表、users用户表。SELECT u.name, o.order_no, o.amount FROM orders o INNER JOIN users u ON o.user_id u.id;INNER JOIN只返回两边能匹配上的数据好比合租只算双向认识的室友LEFT JOIN返回左表全部数据右表没有匹配就填 NULL好比不管你认不认识左边住的人都会出现在名单里。面试题里经常考这两者的区别除了说起条数差别最好再补一句“LEFT JOIN 能得到左表全量而 INNER JOIN 只保留匹配到的交集”。2.5 窗口函数与存储过程面试加分项我见过不少新人基础查询写得很溜一碰到窗口函数就懵。窗口函数简单理解就是“给每一行数据计算一个基于分组窗口的聚合结果但并不合并行”。最经典的场景是排名按年龄为每个用户排名。SELECT name, age, ROW_NUMBER() OVER (ORDER BY age DESC) AS rank_no, RANK() OVER (ORDER BY age DESC) AS rank_val, DENSE_RANK() OVER (ORDER BY age DESC) AS dense_rank_val FROM user;三种排名的区别是高频面试题ROW_NUMBER()不管有没有并列都连续编号RANK()有并列时跳号比如两个并列第 1下一名是第 3DENSE_RANK()有并列也不跳号下一名是第 2。用月度销售排名的场景来记最合适。窗口函数还能解决前面提到的“按某列去重取最新一条”的问题SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY phone ORDER BY create_time DESC) AS rn FROM user ) t WHERE t.rn 1;PARTITION BY phone相当于按手机号分组每组内按创建时间倒序编号取rn1就是每个手机号最新一条。子查询里的别名t是必须的MySQL 要求派生表必须有别名这是一个很多人都遇到过的报错。存储过程在工作中用得不频繁但面试偶尔会问。创建一个简单的存储过程DELIMITER // CREATE PROCEDURE GetUserById(IN userId INT) BEGIN SELECT id, name, age FROM user WHERE id userId; END // DELIMITER ;DELIMITER是用来临时改变语句分隔符的因为存储过程内部有多个分号如果还用默认分号MySQL 会在半路就截断执行。调用存储过程用CALL GetUserById(1);。3. 面试进阶事务、锁、索引与慢 SQL 优化3.1 事务 ACID 与隔离级别转账案例讲透事务是数据库面试的绝对重点也是最容易答得虚的部分。面试官问“事务的特性是什么”如果只是把 ACID 四个字母背出来基本等于没答。要结合场景讲比如转账A 给 B 转 100 元中间涉及扣款和加款两条 SQL要么都成功要么都失败。原子性这个转账过程是一个不可分割的最小单元要么全部完成要么全部回滚。底层靠 undo log 实现。一致性转账前后总金额不变数据库从一个一致状态到另一个一致状态。隔离性两个事务同时转账互不干扰靠锁和 MVCC 实现。持久性事务一旦提交修改就永久保存即使宕机也不会丢靠 redo log 实现。事务的基本语法很直接START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT;如果中间某条语句出错执行ROLLBACK;回滚。隔离级别是事务的高级考点MySQL InnoDB 默认是REPEATABLE READ可重复读。面试时拿出下面这个对照表基本就稳了隔离级别脏读不可重复读幻读READ UNCOMMITTED可能可能可能READ COMMITTED不会可能可能REPEATABLE READ不会不会可能InnoDB 通过间隙锁解决SERIALIZABLE不会不会不会解释三个概念脏读是读到别的事务未提交的数据不可重复读是同一事务里两次读同一行数据结果不一样幻读是同一事务里两次范围查询得到的结果行数不一样比如第一次查到 1 条第二次变成 2 条。实际开发中怎么设置隔离级别用SET TRANSACTION ISOLATION LEVEL READ COMMITTED;或者直接改配置文件。多数互联网业务使用 READ COMMITTED 级别就够了因为可重复读虽然能避免部分问题但间隙锁会提高死锁概率。3.2 MySQL 锁的分类全局锁、表锁、行锁、间隙锁“MySQL 锁的分类”同样是搜索热词。面试答锁的分类建议按粒度从大到小讲。全局锁锁整个数据库实例执行FLUSH TABLES WITH READ LOCK;后所有库只能读不能写典型应用是做整库备份时保证一致性。表级锁锁整张表。MyISAM 引擎只支持表锁InnoDB 主要用行锁但也存在某些情况下的表锁比如没有索引的更新操作只能全表扫描逐行加锁。行级锁InnoDB 最核心的锁机制分为共享锁S 锁读锁和排他锁X 锁写锁。日常执行的UPDATE、DELETE、INSERT都会自动加排他行锁SELECT默认不加锁但可以用SELECT ... FOR UPDATE加排他锁或SELECT ... LOCK IN SHARE MODE加共享锁。从另一个维度可以分悲观锁和乐观锁悲观锁是“我认定你会跟我抢所以在操作前先把数据锁住”实现方式就是FOR UPDATE乐观锁是“我默认没人和我抢更新时检查版本号”典型实现是在表里加version字段更新时UPDATE table SET ... WHERE id ? AND version ?如果影响行数为 0 则重新读取再重试。间隙锁和临键锁比较进阶。间隙锁锁的是一个区间而不是具体行用来解决幻读它锁住的是两个索引记录之间的间隙。临键锁是间隙锁和行锁的组合。InnoDB 的 REPEATABLE READ 隔离级别下范围查询会触发间隙锁这也是为什么高并发场景下把隔离级别降到 READ COMMITTED 能降低死锁概率的原因。3.3 EXPLAIN 与索引优化慢 SQL 三板斧面试官问“慢 SQL 怎么优化”本质是考察你对索引和查询计划的理解。我一般按三步走。第一步先用 EXPLAIN 看执行计划。在 SELECT 前加上EXPLAIN关键字MySQL 会展示这条 SQL 的执行路径。最核心的列是type从好到差依次是system、const、eq_ref、ref、range、index、ALL。看到ALL就是全表扫描意味着 SQL 有优化空间看到ref或range就是走了索引质量不错key列显示了实际用到的索引名称如果为 NULL 说明没走索引。第二步针对查询条件建索引。最常见的索引策略是给 WHERE 条件、JOIN 字段和 ORDER BY 字段建立索引。比如按手机号查用户CREATE INDEX idx_phone ON user(phone);联合索引要理解最左前缀原则比如建立INDEX idx_name_age (name, age)那么查询条件里不包含name时age索引无法生效。这就像查字典必须先按拼音首字母定位再按第二个字母筛选。第三步改写 SQL 避免索引失效。常见坑有对索引列使用函数比如WHERE DATE(create_time) 2025-01-01导致索引失效改成create_time 2025-01-01 AND create_time 2025-01-02隐式类型转换比如手机号是 varchar 类型查询时写phone 13900001234MySQL 会做类型转换使索引失效应该写phone 13900001234SELECT *只取需要的字段尽量走覆盖索引索引里已经包含要查询的字段不需要回表。还有一个优化点是大分页问题。LIMIT 2000000, 20要扫前 200 万行再丢弃性能很差。优化方式是先查主键再做连接SELECT * FROM user WHERE id ( SELECT id FROM user ORDER BY id LIMIT 2000000, 1 ) ORDER BY id LIMIT 20;这个技巧效率提升非常明显面试时能讲出来是加分项。3.4 从 SQL 注入到防御一句“万能密码”背后的原理SQL 注入是个安全话题网上搜“sql 注入万能密码绕过”能看到很多攻击载荷示例。作为开发者我的态度很明确这方面的知识可以了解原理但精力应该放在防御上而不是研究怎么绕过。有些 CTF 比赛里会有 SQL 注入题目比如搜索词里提到的入门题用来练习安全分析是可以的但日常开发中你要做的是堵住漏洞。SQL 注入的本质是程序把用户输入拼进了 SQL 语句里。比如登录场景String sql SELECT * FROM user WHERE name userName AND pwd password ;如果用户在用户名输入框里填了 OR 11 --那拼出来的 SQL 就变成SELECT * FROM user WHERE name OR 11 -- AND pwd --把后面的密码条件注释掉了OR 11恒为真攻击者不需要知道密码就能拿到用户信息。这就是所谓的“万能密码”思路。防御手段最核心的一条是使用参数化查询绝不手工拼接 SQL。PreparedStatement ps conn.prepareStatement( SELECT * FROM user WHERE name ? AND pwd ? ); ps.setString(1, userName); ps.setString(2, password); ResultSet rs ps.executeQuery();PreparedStatement 会把用户输入当纯数据处理而不是当成 SQL 语法的一部分。另外配合最小权限原则数据库账号只给应用所需的权限不要把 root 账号直接写在代码里。3.5 高频面试习题速答附答案拆解整理几道出现频率很高的 SQL 面试题附上解题思路基本是“面试前背一遍手写不出错”的级别。第一题查找第 N 高的工资。SELECT DISTINCT salary FROM employee ORDER BY salary DESC LIMIT 1 OFFSET N-1;如果要求“第 3 高”OFFSET 就是 2。这道题的变种是“没有第 N 高时返回空”可以用子查询或者函数包一层。第二题统计每个部门的平均工资并展示平均工资大于 5000 的部门。SELECT dept_id, AVG(salary) AS avg_salary FROM employee GROUP BY dept_id HAVING avg_salary 5000;第三题查找重复出现两次以上的手机号。SELECT phone FROM user GROUP BY phone HAVING COUNT(*) 2;第四题删除重复数据只保留 id 最小的那条。DELETE u1 FROM user u1 INNER JOIN user u2 ON u1.phone u2.phone AND u1.id u2.id;也可以提前给重复数据编号然后删除编号不是 1 的DELETE FROM user WHERE id NOT IN ( SELECT MIN(id) FROM user GROUP BY phone );第五题用窗口函数求每个部门工资最高的员工。SELECT dept_id, name, salary FROM ( SELECT dept_id, name, salary, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk FROM employee ) t WHERE t.rk 1;第六题MySQL 为什么要用 utf8mb4 而不是 utf8因为 MySQL 里的utf8是阉割版最多存 3 个字节存不了 emoji 表情和一些生僻字utf8mb4才是真正的 UTF-8 四字节编码兼容更全。第七题char 和 varchar 的区别char 是定长varchar 是变长char 适合存储固定长度的数据比如手机号、身份证varchar 适合长度不固定的文本。varchar 在 InnoDB 中存储时会有额外的长度前缀读取速度快不过在排序等操作中更耗空间。4. 实际开发中的辅助技能从建表 SQL 到数据导入4.1 MyBatis-Plus 根据 Java 实体类生成建表 SQL搜索热词里有“mybatisplus 根据 java 实体类生成创建表的 sql 语句”这确实是开发中的高频操作。MyBatis-Plus 的代码生成器可以生成实体类、Mapper、Service但它本身并不直接支持“根据 Java 实体类生成建表 SQL”。实际工作中常用两种思路。第一种在实体类上写好注解标签然后通过一个工具方法把字段反射出来拼接 DDL。示例实体类Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; TableField(name) private String name; TableField(age) private Integer age; }然后写一个简单的生成器通过反射读取实体类的TableField、TableId、TableName注解拼出 CREATE TABLE 语句。这个方案能自动化但我们通常更推荐第二种直接用 MyBatis-Plus 的TableInfoHelper获取表结构信息再把字段映射为 DDL 类型。第二种更稳妥的思路是写一份 SQL 脚本文件放在项目的resources/db目录下配合启动时执行。比如CREATE TABLE IF NOT EXISTS user ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL DEFAULT , age INT NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;在application.yml里配上spring.sql.init.mode: always和spring.sql.init.schema-locations: classpath:db/schema.sql应用启动时自动建表。对比下来手动写建表 SQL 更可控字段类型映射也更精确反射生成适合动态业务但对字段类型、索引设定的控制力弱。4.2 Navicat 导入 SQL 数据三大常踩坑Navicat 是数据库管理工具里的常青树日常做表结构设计、数据导入导出都很方便。但导入 SQL 文件时有三个坑很典型。第一个坑是字符集不对导致乱码。SQL 文件本身是 utf8mb4但 Navicat 默认连接字符集可能是其他编码。解决方式是导入之前先把连接编码设置为utf8mb4在“编辑连接 → 高级”里勾选“使用 MySql 字符集 utf8mb4”导入时在向导的“高级”选项里明确指定编码。第二个坑是外键依赖顺序错了导致导入失败。如果 SQL 文件同时包含父表和子表而父表还没创建子表的外键约束就建立不起来。处理方案有两种要么在导入前临时关闭外键约束检查在 SQL 文件开头加上SET FOREIGN_KEY_CHECKS 0;末尾加回SET FOREIGN_KEY_CHECKS 1;要么把建表语句按依赖顺序排好。第三个坑是单个大 SQL 执行超时或内存不足。导入几 GB 的 SQL 备份时Navicat 默认的超时时间比较短并且max_allowed_packet太小会报Packet too large。在 MySQL 配置文件[mysqld]下增加max_allowed_packet256M重启服务再在 Navicat 的“查询 → 高级”里调大执行超时时间。4.3 JDBC 连接 MySQL 与参数化查询如果是 Java 后端方向JDBC 是绕不开的。一个基础连接代码如下Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/shop_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4; String user root; String password 123456; Connection conn DriverManager.getConnection(url, user, password);useSSLfalse在本地开发时能避免证书警告serverTimezoneAsia/Shanghai处理时区差异characterEncodingutf8mb4防止中文乱码。查询时用 PreparedStatement 而不是 StatementString sql SELECT id, name FROM user WHERE age ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, 18); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getInt(id) - rs.getString(name)); } } } catch (SQLException e) { e.printStackTrace(); }还有一套经典的坑是“Public Key Retrieval is not allowed”这是 MySQL 8.0 使用 caching_sha2_password 认证插件时的问题解决方案是在 JDBC URL 后面加allowPublicKeyRetrievaltrue。5. 常见问题与排查技巧实录5.1 连接与认证问题“Access denied for user rootlocalhost”密码错了或者 root 账号的 host 限制。如果是刚初始化完忘记密码最直接的方案是用--skip-grant-tables模式启动 MySQL 再改密码但生产环境不建议随便用。另一种方案是重新执行初始化命令清掉 data 目录重来测试环境反正没有重要数据。“Public Key Retrieval is not allowed”上一章提过JDBC 连接 8.0 时常见加allowPublicKeyRetrievaltrue。如果是 Navicat 连接报这个在连接属性的高级选项卡里勾选“使用加密连接”并允许公钥检索。MySQL 命令卡在mysql -u root -p回车后一般不是卡住是提示输入密码直接敲密码回车就行界面上不会显示任何字符。如果你感觉回车后没反应试一下输入密码再回车。5.2 SQL 执行与字符集问题“Every derived table must have its own alias”MySQL 要求每个子查询派生表都必须有别名。写完子查询后忘了在后面加AS t就会报这个错。解决方案是在子查询闭合括号后面加别名。“Expression #1 of SELECT list is not in GROUP BY clause”这是ONLY_FULL_GROUP_BY模式开启导致的。SQL 规范要求GROUP BY后面的列和SELECT的非聚合列必须一致。两种解决方式要么把SELECT里不在分组里的列也加进GROUP BY要么在查询时用ANY_VALUE()包一下不需要分组的字段。不建议直接关掉这个模式它其实是帮你在避免代码隐患。中文乱码。首先确认三处编码一致数据库字符集SHOW VARIABLES LIKE character_set_database;、连接字符集SET NAMES utf8mb4;、表字段字符集。三处都统一成 utf8mb4乱码基本不会再出现。另外注意 SQL 文件的编码格式如果用 Word 之类的工具另存为带 BOM 的 UTF-8导入时前几个字符可能被 BOM 吃掉导致首行解析失败。5.3 性能与进程问题速查表日常运维中问最多的几个量级问题整理成速查表现象排查命令常见处理数据库连接数被打满SHOW VARIABLES LIKE max_connections;SHOW STATUS LIKE Threads_connected;调大 max_connections优化长连接检查是否有 MySQL 连接泄漏CPU 飙升SHOW PROCESSLIST;找 State 为 Copying to tmp table 或长时间 Running 的语句杀掉慢会话KILL ID;对慢 SQL 做 EXPLAIN 优化索引查询明显变慢EXPLAIN SELECT ...;看 type 是不是 ALLkey 是否为 NULL根据 WHERE/JOIN/ORDER BY 字段补索引死锁报错查看SHOW ENGINE INNODB STATUS;中的 LATEST DETECTED DEADLOCK让事务尽量短统一 SQL 更新顺序降低隔离级别到 READ COMMITTED磁盘空间不足SHOW TABLE STATUS;查看 Data_length检查 binlog 大小清理 binlog清理无用表考虑归档历史数据还有一个容易被忽略的“慢”原因表长时间没做 ANALYZE统计信息过旧优化器选错索引。执行一次ANALYZE TABLE user;往往立竿见影。最后说一个我自己的习惯。带新人时我会要求他们装好 MySQL 之后先自己把“建库、建表、插入 20 条测试数据、做 5 种查询、再改一条数据”这套流程完整走一遍不要一上来就背面试题。环境踩过的坑和手感是背不出来的只要这个流程能独立走通后面无论是继续啃 SQL 进阶、看索引原理还是刷面试题都会顺畅很多。如果你装的是 Docker 版我建议额外在 Windows 上把 zip 版也装一次因为运维和生产环境大概率不是 Docker提前把my.ini、net start、mysqld --initialize这套流程搞熟以后遇到服务器部署会非常从容。
返回列表