ARTICLE DETAIL

资讯详情

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

MySQL从入门到入魔:安装、索引、慢查询与排错实战

MySQL从入门到入魔:安装、索引、慢查询与排错实战 看到这个标题我自己都想笑——当年入门 MySQL 的时候确实是从“入土”到“入魔”的体验。最离谱的一次装个 MySQL 8.0 从下午折腾到半夜最后发现是 data 目录权限的锅。所以这篇东西我准备按自己真实的学习路线来写从零装一个能用的环境开始把架构原理、常用 SQL、索引、存储过程这些硬骨头一块块啃下来再打包送上我这些年踩过的坑和面试整理。不用搜索引擎词条那种一条条罗列的方式而是按“入门 → 进阶 → 入魔”的完整路径串起来让每一个mysql xxx的热搜词都能在这篇文章里找到对应的落点。这篇文章适合什么人来读一类是刚装好 MySQL 还在懵圈的新手把第一到第三章走一遍日常开发基本够用另一类是用了两三年 MySQL 但总觉得差点火候的同学重点看第四章索引和第六章的排错思路里面有不少常规文档里不会写的细节。我已经默认你电脑有基本的命令行操作能力剩下的照着一步步做就行。1. 装一个能用的MySQL压缩版、Docker和图形工具那些坑1.1 压缩版安装我为什么推荐而不是直接点下一步MySQL 的安装方式粗分就是三种msi 向导安装、zip 压缩版安装、包管理器或 Docker 安装。网上大量教程让你下 msi 一路 Next确实省事但我个人强烈建议新手至少用一次压缩版。原因是压缩版把初始化、配置文件、服务注册这些动作全部暴露给你装完你对 MySQL 的运行机制会有直觉后面排查问题会顺手很多。压缩版的具体步骤以目前主流的 MySQL 8.0.46 为例去 MySQL 官网下载 zip 压缩包注意选MySQL Community Server不要选成企业版操作系统选Microsoft Windows。解压到一个干净路径比如D:\mysql-8.0.46-winx64。这里有个小坑路径里不要有中文、空格和特殊符号否则后面初始化的时候经常报莫名其妙的错误。在解压目录下新建my.ini配置文件最小可用的配置长这样[mysqld] basedirD:/mysql-8.0.46-winx64 datadirD:/mysql-8.0.46-winx64/data port3306 character-set-serverutf8mb4以管理员身份打开命令行进入 bin 目录执行初始化命令。这里有两个选择mysqld --initialize-insecure会生成一个 root 空密码账号适合本地学习mysqld --initialize会生成一个随机临时密码密码打印在data目录下的.err日志文件里。我建议本地学习用--initialize-insecure省得找半天密码。注册 Windows 服务执行mysqld --install然后net start mysql启动服务。如果提示Install/Remove of the Service Denied说明你的命令行没有管理员权限这是最常见的安装报错之一。登录并修改密码mysql -u root -p ALTER USER rootlocalhost IDENTIFIED BY 你的新密码; FLUSH PRIVILEGES;整个过程走一遍你对 MySQL 的目录结构、配置加载顺序、服务启动方式都会有一个具象的认识。以后再遇到mysql 服务无法启动这类问题第一反应就是去翻 data 目录下的.err日志而不是瞎猜。1.2 端口号、依赖包和那些装不上的“离线”情况热搜词里能看到不少“端口号”“离线依赖”相关的词说明很多人都卡在类似位置上。先说端口号。MySQL 默认监听 3306如果你装完发现net start mysql起来了但客户端连不上第一件事就是看 3306 是不是被占用了。Windows 下用netstat -ano | findstr 3306Linux 下用ss -lntp | grep 3306。如果被占用要么把占用进程解决了要么在my.ini里改port3307但要记住改完之后所有连接串都要跟着改。再说 Linux 离线安装。CentOS 8 上离线安装 MySQL 8.0 最常见的报错就是缺依赖尤其是libaio和libncurses。一个可靠的做法是先把所有需要的 rpm 包下载到本地目录然后用yum localinstall *.rpm或rpm -ivh按依赖顺序安装。不要试图绕过依赖直接用rpm -ivh --nodeps当时装上了后面启动 MySQL 的时候会以更难看的方式炸给你看。Windows 下则一定要确保 Microsoft Visual C Redistributable 已安装这个问题很隐蔽MySQL 8.0 在缺少 VC 运行库的时候mysqld可能闪退或直接提示找不到vcruntime140.dll。至于热搜词里出现“mysql 4.1.22下载”这样的字眼我得劝一句除非你是为了考古老系统否则别碰 4.x 了。4.1 时代的字符集、优化器、性能跟我们今天用的 5.7 / 8.0 完全是两个时代的东西光是 utf8mb4 的支持就是 5.5 之后才完善的。今天新项目起步直接用 8.0在生产环境跑了很多年的老系统选 5.7 维护兼容性也可以理解但新库不建议再用 5.7因为官方维护期已经进入尾声了。1.3 图形工具与DockerWorkbench、Navicat、DBeaver怎么选装好服务端还得有个趁手的客户端。MySQL 官方的 Workbench 功能其实够用连接管理、SQL 编辑器、ER 图、性能监控都有对于新手来说完全不需要额外找工具。它的使用逻辑也很简单打开后点加号新建连接填主机名、端口、用户名密码测试连接成功后进入主界面左边是数据库导航树中间是 SQL 编辑器选中一段 SQL 按 CtrlEnter 执行。但如果你习惯老的 Navicat 那一套操作像navigator for mysql 免费版、navicat 17 for mysql注册码这些热搜词我的看法是Navicat 确实顺手可视化建表、数据导入导出、模型同步这些功能做得非常成熟但它是一款收费商业软件。想长期用的要么买正版要么用开源替代品 DBeaver Community界面类似支持几乎所有主流数据库日常开发完全够用。不要在奇怪的地方找注册码下载下来的东西轻则带广告弹窗重则是什么东西你自己想。Docker 装 MySQL 是另一个热门姿势。一条命令就能拉起来一个实例docker run -d \ --name mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -v /my/data:/var/lib/mysql \ mysql:8.0这里最重要的是-v数据卷挂载否则容器一删数据全部灰飞烟灭。生产环境还要额外考虑配置挂载和日志挂载。Docker 方式最大的好处是版本切换成本极低想测 5.7 和 8.0 的差异分别起两个容器就可以了不污染本机环境。最后补一句“数据库连接池”的问题。为什么应用连数据库不用“每个请求建一个连接”因为建立数据库连接是重操作频繁创建销毁会拖垮性能。连接池的本质就是预先建立一批连接放在池子里应用用的时候借、用完还回来。常见参数有initialSize初始连接数、maxActive最大连接数、maxWait获取连接最大等待时间这些配置在 HikariCP、Druid 这些中间件里都是老生常谈理解了连接池的思路后面看到一堆连接池配置就不会怵。2. 一条SQL从连接到返回中间发生了什么2.1 MySQL的分层架构连接、解析、优化、执行很多人用 MySQL 用了几年只知道它是一个数据库却不知道一条 SQL 进去之后到底走了哪些环节。其实 MySQL 的逻辑架构是清楚的三层连接层、Server 层、存储引擎层。连接层负责处理连接管理、鉴权认证和请求转发。相当于公司前台的保安先确认你有没有门禁卡你是什么级别的人再决定放你进哪一栋楼。Server 层是核心大脑负责解析 SQL、生成执行计划、通过执行器调用存储引擎的接口。这一层不关心数据怎么存储只关心怎么把 SQL 翻译成对存储引擎的操作。存储引擎层是真正的数据仓库InnoDB、MyISAM、Memory 都在这一层负责具体的数据读写和索引维护。平时一直说“MySQL 是插件式存储引擎架构”就是因为你可以在建表时自由选择存储引擎。用一段文字流程来描述整条链路的执行顺序客户端连接 → 连接器校验账号密码建立连接 → 分析器词法分析、语法分析生成语法树 → 优化器决定用哪个索引、按什么顺序连接表生成执行计划 → 执行器调用存储引擎接口逐行读取返回结果 → 返回客户端你在 MySQL 里执行EXPLAIN SELECT ...看到的那些列信息其实就是优化器给出的执行计划。比如type列从好到差依次是system const eq_ref ref range index ALL看到一个ALL就说明是全表扫描如果表很大这条 SQL 基本就是慢查询的罪魁祸首。2.2 UPDATE为什么比SELECT多了一堆日志操作如果你理解了 SELECT 的执行链路再来看 UPDATE就会多一个疑问为什么更新数据要牵扯redo log、undo log、binlog这一堆东西原因很简单数据库不信任你的操作系统也不信任你的磁盘。万一执行到一半机器断电了内存里的数据页还没刷到磁盘数据库重启后怎么恢复答案就是日志。这里可以打一个特别生活化的比方redo log是财务当天记的流水账先把账记下来回头再誊写到正式账本上binlog是部门的工作周报记录了每一笔变更的事实用于归档、同步和恢复undo log则是草稿纸用于事务回滚时抹掉错误的操作。InnoDB 的更新路径大致是先把要更新的行从磁盘读到内存的 Buffer Pool修改内存页同时生成 undo log 用于回滚再写 redo log 并标记为 prepare 状态然后写 binlog最后把 redo log 提交整个事务才算完成。这套机制里 redo log 和 binlog 的“两阶段提交”设计就是为了保证两份日志的一致性确保任何时候崩溃恢复出来的数据都对得上。理解这些不是让你当 DBA而是以后再遇到“为什么数据库这么慢”“为什么刚更新的数据突然不见了”“为什么主从数据对不上”这些问题时脑子里有排查方向而不是只会重启。2.3 InnoDB和MyISAM到底差在哪存储引擎这个知识点面试爱问日常开发也躲不开。MySQL 5.5 之前默认是 MyISAM5.5 之后默认改成 InnoDB这个变化本身就是答案InnoDB 支持事务、支持行级锁、支持外键、支持崩溃恢复而 MyISAM 都不支持。所以 MyISAM 只适合那些只读、不要求数据一致性的场景比如数据仓库里的历史归档表。这里提醒一句在建表的时候显式指定ENGINEMyISAM的场景我只建议在少数纯读场景用。大部分业务系统尤其涉及金额、库存、订单的必须用 InnoDB。否则一旦并发一上来表级锁会把所有写操作串行化性能直线下降。2.4 大小写问题一次排查让人怀疑人生热搜词里那条“kingbase mysql模式字符串不区分大小写咋回事”其实牵涉到 MySQL 两个完全不同层面的敏感性问题很多人混在一起了导致排查越搞越乱。第一个层面是表名大小写敏感由lower_case_table_names参数控制。值为 0 时表名区分大小写Linux 默认值为 1 时表名不区分大小写Windows 默认。最坑的是同一个项目开发用 Windows、生产用 Linux结果本地写了个Select * From User能跑生产一执行就报Table xxx.User doesnt exist。正是因为这个参数在初始化后修改会导致数据访问异常所以最好在初始化前就统一规划团队里所有人、所有环境都用同一个值。第二个层面是查询字符串值的大小写这跟 collation排序规则有关。utf8mb4_general_ci中的ci就是 case insensitive不区分大小写所以WHERE name zhangsan也能查到ZHANGSAN如果你要区分可以改用utf8mb4_bin它会按字节精确比较。金仓数据库的 MySQL 兼容模式默认不区分大小写通常就是因为默认 collation 选了不区分大小写的规则调整成 bin 规则即可。很多人大费周章去改程序代码最后发现改一个 collation 就解决了这就是对底层机制理解不到位的代价。3. 最常用的SQL也是最容易写错的SQL3.1 UPDATE子查询的同表互斥ERROR 1093的坑日常开发里UPDATE配合SELECT子查询是非常常见的操作但 MySQL 有个特别容易踩的坑你不能在UPDATE的目标表上直接做子查询。比如这么写UPDATE student SET score 100 WHERE id IN (SELECT id FROM student WHERE name 张三);MySQL 会直接甩一个错误You cant specify target table student for update in FROM clause。原因不复杂MySQL 在执行UPDATE时会把子查询的表和目标表被视为同一个表这种操作可能导致不可预期的行为所以干脆禁止。解决办法各路教程都有核心就一句话把子查询再包一层让 MySQL 认为你查的是一个“派生表”不是目标表UPDATE student SET score 100 WHERE id IN (SELECT id FROM (SELECT id FROM student WHERE name 张三) t);这个技巧在DELETE语句里同样适用。我自己就在这个坑里摔过好几次每次写同表更新子查询条件反射就会多加一层包装算是一种肌肉记忆了。这也是面试题“mysql中更新子查询”最常见的变体面试官基本就看你知道不知道这个 1093 报错。3.2 排序、NULL值和那些被你忽略的默认行为说到排序ORDER BY大家天天写但有几个细节经常被忽略。第一个是 NULL 值的排列位置。MySQL 默认排序时NULL 被认为是小于任何值的所以在升序ASC时 NULL 排最前面降序DESC时 NULL 排最后面。如果你业务上希望 NULL 排最后有的人会到处用ORDER BY field IS NULL, field ASC这种技巧关键是得知道为什么这么写field IS NULL这个表达式的计算结果本身是一个 0/1 值先按它排序NULL 记录的IS NULL结果是 1自然就排到了非 NULL 记录0的后面。第二个是ORDER BY与索引的关系。如果排序字段有索引MySQL 直接有序读取索引即可性能极高如果没有索引就要在sort_buffer_size里做内存排序filesort数据量超过内存还要转磁盘临时文件慢查询就产生了。所以“为什么我的排序这么慢”这个问题绝大多数时候答案都是“排序字段没索引”或者“排序字段和 where 条件没组成联合索引”。第三个容易被问懵的是ORDER BY RAND()。如果你写SELECT * FROM table ORDER BY RAND() LIMIT 10MySQL 会对全表每一行生成随机数再排序数据量大时极其恐怖。需要随机取 N 条记录的更优做法是取MAX(id)和MIN(id)在区间内生成随机 id 再查。3.3 INT(5)不等于只能存5位数整数类型的三个迷思热搜词里有一条“mysql中int5”初看以为是算术题实际上这背后是两个经典误解。第一个误解是INT(5)是不是代表这个字段最多存 5 位数。不是。INT类型的存储范围是固定的有符号从-2147483648到2147483647无符号从0到4294967295。括号里的数字只是显示宽度display width只有配合ZEROFILL属性时才有效果比如INT(5) ZEROFILL存储 12 时显示为00012。如果你把INT(5)误当成长度限制存了个大数进去它照样存得下不会报错反而会让后期接手的同事困惑。第二个误解是整数溢出。INT最大就到2147483647如果你在应用层传过来一个超过这个范围的数MySQL 在严格模式下会报Out of range value错误非严格模式下则会进行截断处理存一个最大值进去造成数据失真。所以金额、大数这类场景用INT就要谨慎了。热搜词里那个“int5”我猜问的是SELECT int_col 5 FROM table这类算术运算做加法没问题但要注意参与运算时若另一侧是字符串MySQL 会进行隐式类型转换把字符串转成数字转换失败就可能变成 0这种隐式转换一旦发生在索引列上索引就失效了。第三个痛点是“为什么我存手机号用 INT 存坏了”。手机号一般是 11 位早已超过 INT 上限应该用BIGINT或者直接VARCHAR。选VARCHAR的好处是手机号前面的 0、区号、分隔符等格式能被保留而且手机号作为查询条件时字符串等值匹配的语义比整数更清晰。这类基础问题在面试和实际开发里出现的频率高到离谱非常值得认真对待。3.4 修改表结构ALTER TABLE是用前需学会刹车热搜词“mysql数据库修改结构”对应的就是ALTER TABLE系列操作。它的语法本身不复杂-- 添加字段 ALTER TABLE student ADD COLUMN phone VARCHAR(20) AFTER name; -- 修改字段类型 ALTER TABLE student MODIFY COLUMN phone VARCHAR(30); -- 重命名字段 ALTER TABLE student CHANGE COLUMN phone mobile VARCHAR(30); -- 删除字段 ALTER TABLE student DROP COLUMN mobile; -- 添加索引 ALTER TABLE student ADD INDEX idx_name (name);真正有技术含量的是“在大表上改结构”这件事。早期 MySQL 版本里不少ALTER TABLE操作会拷贝整张表期间表被锁住读写全部阻塞生产环境一次 ALTER 大表就可能造成线上事故。MySQL 5.6 以后引入 Online DDL许多操作可以做到在线进行比如ADD INDEX默认就支持 Online DDL。MySQL 8.0 又更进一步部分ADD COLUMN操作支持ALGORITHMINSTANT能秒级完成。但我的忠告是不管支持什么算法大表的 DDL 都不要在业务高峰期执行。哪怕底层用了 INSTANTDDL 期间的元数据锁等待、复制延迟依然有可能拖垮主从。稳妥操作是把大表变更安排在维护窗口执行前先看表大小用SHOW TABLE STATUS LIKE table_name看 Engine 和 Rows心里有数再动手。3.5 最常用的SQL速查别背大全背这些就够了市面上各种“MySQL 命令大全”几百条命令堆成山真正常用的其实就那几十条。我自己整理了一份精华版覆盖建库建表、增删改查、索引、权限、备份恢复够用且好记。-- 数据库操作 CREATE DATABASE db1 DEFAULT CHARACTER SET utf8mb4; USE db1; SHOW DATABASES; DROP DATABASE db1; -- 表操作 CREATE TABLE user ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, age INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; DESC user; -- 查看表结构 SHOW CREATE TABLE user; -- 查看建表语句 ALTER TABLE user ADD COLUMN email VARCHAR(100); -- 增删改查 INSERT INTO user (name, age) VALUES (张三, 25); UPDATE user SET age 26 WHERE name 张三; DELETE FROM user WHERE name 张三; SELECT * FROM user WHERE age BETWEEN 20 AND 30 ORDER BY age DESC LIMIT 10; -- 聚合 SELECT age, COUNT(*) FROM user GROUP BY age HAVING COUNT(*) 1; -- 索引 CREATE INDEX idx_user_name ON user (name); CREATE UNIQUE INDEX idx_user_email ON user (email); DROP INDEX idx_user_name ON user; -- 用户权限 CREATE USER app% IDENTIFIED BY password; GRANT SELECT, INSERT, UPDATE, DELETE ON db1.* TO app%; SHOW GRANTS FOR app%; -- 备份与恢复 -- mysqldump -u root -p db1 db1.sql -- mysql -u root -p db1 db1.sql这一节通篇看下来你会发现 SQL 的基础语法真的简单难的是“知道哪些写法会让 MySQL 难受”。这也正好衔接下一章的重点索引。4. 索引不是建了就完了创建姿势、底层原理与失效现场4.1 建索引的三种姿势和唯一索引的“清理先于创建”创建索引主要有三种方式。第一种是建表时直接指定CREATE TABLE user ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, email VARCHAR(100) NOT NULL, name VARCHAR(50), UNIQUE KEY uk_email (email), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第二种是对已有表加索引CREATE INDEX idx_name ON user (name); ALTER TABLE user ADD INDEX idx_age (age);第三种是创建唯一索引CREATE UNIQUE INDEX uk_email ON user (email);这里有一个生产环境非常常见的场景热搜词“mysql设置唯一已经有重复数据库”。意思就是你想给某个字段加唯一约束但这个字段在现有数据里已经有重复值了直接创建会报Duplicate entry错误。正确的处理链路是先查重复数据再决定清理策略最后建唯一索引。-- 找出重复记录 SELECT email, COUNT(*) FROM user GROUP BY email HAVING COUNT(*) 1; -- 保留每组里 id 最小的删除其他重复记录 DELETE u1 FROM user u1 INNER JOIN user u2 WHERE u1.email u2.email AND u1.id u2.id; -- 再创建唯一索引 CREATE UNIQUE INDEX uk_email ON user (email);有同学可能会问MySQL 8.0 里没有ALTER IGNORE TABLE ... ADD UNIQUE INDEX这种暴力去重的方式了那就老老实实按上面的步骤来。这种“先查后改再建”的思路是所有数据库结构变更的通用安全姿势不只是 MySQL 专属。4.2 为什么偏偏是B树从二叉搜索树聊起“MySQL 索引为什么用 B 树”是面试高频题也是理解索引原理的必经之路。绝大多数人只是背了答案没理解根本原因。我尝试用一个 5 分钟的推理把它讲透。假设我们需要一个数据结构来加速查找最简单的是二叉搜索树BST查找时间复杂度是 O(log n)看起来不错。但问题在于数据库索引是存在磁盘上的每次读一个节点就是一次磁盘 IO而磁盘 IO 的速度比内存慢几个数量级。BST 每个节点只存一个键和两个指针树的高度会很高比如 100 万条数据高度大约 20 层最坏情况下查询要走 20 次磁盘 IO这太慢了。于是我们想到 B 树让每个节点存储多个键和多个指针树变得又矮又宽。但 B 树的缺点是所有节点都存储数据范围查询时要反复在中序节点和叶子节点之间切换磁盘 IO 次数依然不理想。B 树进一步优化非叶子节点只存键和指针不存数据这样每个节点能容纳的键数量更多树的高度进一步降低。以 InnoDB 一个页默认 16KB 为例三层 B 树就能容纳上千万条记录。而且 B 树的叶子节点通过链表相连范围查询只要找到起点顺着链表顺序往下扫即可这正是数据库最频繁的“范围查询”场景需要的。以下是一个简化对比数据结构树高是否存数据范围查询磁盘IO二叉搜索树高是差多B树低是一般中等B树更低仅叶子节点优秀少InnoDB 的主键索引本身就是 B 树。所以当你用主键查数据时走的是聚簇索引叶子节点直接保存整行数据而普通索引的叶子节点保存的是主键值需要再通过主键回表查一整行。理解主键索引和二级索引的差异理解回表、覆盖索引就是从这里延伸出来的。4.3 索引失效现场为什么建了索引还是全表扫建了索引不等于查询就一定走索引。这是我见过最多人吃亏的地方热搜词里“mysql创建索引”“mysql索引”背后大量问题其实都是索引失效。最常见的几个失效场景如下一是违反最左前缀原则。联合索引(a, b, c)当你查询条件只包含b而没包含a时索引大概率失效因为联合索引是按照第一列、第二列、第三列的顺序构建 B 树的跳过第一列相当于在你没翻开总目录的情况下去找具体章节。二是对索引列做函数运算或隐式类型转换。比如WHERE DATE(created_at) 2024-01-01虽然created_at有索引但对它做了函数处理后B 树的有序性就不成立了优化器只能放弃索引。同样WHERE phone 13812345678phone是 VARCHAR右边却是数字MySQL 会把字符列转成数字去比较索引失效。解决办法是老老实实写WHERE phone 13812345678。三是模糊匹配LIKE %abc。左侧通配符出来的时候无法利用 B 树的顺序查找索引失效但LIKE abc%是可以走索引的。四是OR语句中有非索引列。比如WHERE name 张三 OR age 20如果age没索引那整个 OR 条件要走全表扫描。我会建议你每次写完一条稍微复杂的 SQL都习惯性跑一下EXPLAIN看一眼key列确认它用了你预期的索引。这个习惯一旦养成能省下一堆“为什么这么慢”的排查时间。4.4 为什么推荐自增主键一个容易被忽略的设计问题面试还有一个高频问题为什么 InnoDB 表推荐用自增主键而不是 UUID原因还是在 B 树的页分裂上。InnoDB 聚簇索引的叶子节点本身是有序的新插入的数据如果主键是自增的那么在 B 树“最右侧”追加就可以了开新页只需要简单连接。但如果主键是 UUID 这种无序值新插入的主键可能落在已有的叶子节点中间MySQL 不得不把原有节点拆成两部分把数据挪来挪去这个操作叫页分裂会产生大量随机 IO并且让页产生碎片。当然这并不是说业务主键绝对不能是 UUID像分布式场景下需要全局唯一 ID完全可以用雪花算法Snowflake ID生成一个趋势递增的整数主键。关键是理解主键有序性对 InnoDB 的写入性能有实质性影响。5. 存储过程、自动备份脚本与运维细节5.1 存储过程先学会写再决定用不用存储过程现在的名声有点两极分化一边是 DBA 用它跑批量任务另一边是业务开发觉得它难以调试、难以维护。我的看法是可以不用但必须会读、会写基础结构因为老系统里有一堆遗留 SQL 全在存储过程里。一个标准的存储过程基本长这样DELIMITER // CREATE PROCEDURE get_user_by_age(IN min_age INT, OUT total INT) BEGIN SELECT COUNT(*) INTO total FROM user WHERE age min_age; SELECT * FROM user WHERE age min_age; END // DELIMITER ; -- 调用 CALL get_user_by_age(18, total); SELECT total;这里DELIMITER //的用途是把结束符临时改成//否则分号会被 MySQL 当作语句结束标志整个存储过程定义就碎了。这个细节是新手学存储过程时最容易卡壳的地方。存储过程有个特别实用的点是错误处理。热搜词“mysql储存过程错误信息”包含的就是这个需求。比如在批量插入时遇到重复数据想捕获错误而不是中断整个事务可以这样写DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SELECT 事务失败已回滚 AS error_message; END;这段声明放在BEGIN ... END的开头部分告诉 MySQL只要在这个存储过程里发生任何 SQL 异常就执行回滚并返回错误提示。有些场景你想要的是CONTINUE HANDLER也就是捕获错误后继续执行比如批量处理多条记录单条失败不中断任务这在数据清洗场景非常实用。至于什么时候用存储过程我的建议是数据批量处理、定时任务、对性能有极致要求且逻辑稳定的场景可以适当用而业务逻辑频繁变化、需要大量权限控制的场景尽量把逻辑放到应用层否则后面改一行业务逻辑还要专门去更新数据库脚本维护成本极高。5.2 自动备份bat脚本三分钟搭一个本地备份任务“mysql自动备份bat”这个热搜我太有共鸣了因为很多小团队没有专职 DBA数据库备份全靠 Windows 计划任务。这里给你一个我用了很久的批处理脚本直接可改可用echo off set Y%date:~0,4% set M%date:~5,2% set D%date:~8,2% set H%time:~0,2% set MIN%time:~3,2% if %H% 0 set H0 set BACKUP_DIRD:\mysql_backup if not exist %BACKUP_DIR% mkdir %BACKUP_DIR% set BACKUP_FILE%BACKUP_DIR%\backup_%Y%%M%%D%_%H%%MIN%.sql rem 用 mysqldump 导出数据库 D:\mysql-8.0.46-winx64\bin\mysqldump.exe -uroot -p你的密码 --default-character-setutf8mb4 --single-transaction --routines --triggers dbname %BACKUP_FILE% rem 压缩并删除原始 sql 文件 C:\Program Files\7-Zip\7z.exe a -tzip %BACKUP_FILE%.zip %BACKUP_FILE% del %BACKUP_FILE% rem 只保留最近 7 天的备份 forfiles /p %BACKUP_DIR% /s /m *.zip /d -7 /c cmd /c del path echo backup done!几个关键参数要说明--single-transaction对 InnoDB 引擎生效在备份期间开启一个可重复读事务保证数据一致性同时不锁表。如果不加备份大表时可能阻塞线上写入。--routines --triggers把存储过程和触发器也一并导出来否则恢复之后数据库结构不完整。--default-character-setutf8mb4避免字符集不一致导致的乱码问题。脚本写好后放到 Windows 任务计划程序里设置每天凌晨执行一次。这里有个坑任务计划的“起始于”目录最好写上脚本所在目录否则计划任务执行时当前路径不对脚本可能找不到 7z 或者输出路径异常。Linux 下的自动备份思路完全一致用mysqldump加上 crontab 即可只是文件路径和时间获取语法要调整。5.3 不同数据库的生态对比MySQL不是万能答案热搜词里有“postgresql sqllite mysql”这样的搜索说明很多人正在做数据库选型。MySQL 的优势在于生态成熟、资料多、运维简单是绝大多数业务系统的安全牌。PostgreSQL 在复杂查询、JSON、地理空间等方面的能力更强适合分析类需求。SQLite 则是嵌入式场景的王者移动应用和本地小工具常用它不需要独立服务进程。这里我不想替你做选型而是提醒一句连接池、备份、权限管理这些运维手段在不同数据库里名称不一样但思路相通。比如 PostgreSQL 的pg_dump对应 MySQL 的mysqldumpSQLite 的备份就是直接复制文件。你在一套数据库上学到的原理完全能迁移到另一个数据库上。5.4 数据迁移时sqoop连不上MySQL的排查套路大数据工程师常遇到一个搜索词场景“sqoop连接不上mysql”。这里说的是 Apache Sqoop 通过 JDBC 连接 MySQL 进行数据导入导出的问题。常见的报错是Connection refused或Communications link failure。我建议你按这个顺序排查第一步看网络连通性。在 Sqoop 所在机器执行ping MySQL服务器IP通了再执行telnet IP 3306。如果 3306 不通检查 MySQL 所在主机的防火墙是否放行 3306 端口云服务器还要看安全组规则。第二步看 MySQL 用户权限。Sqoop 连接 MySQL 用的账号必须允许从 Sqoop 所在主机的 IP 访问。MySQL 用户由user和host两部分组成rootlocalhost只能本机登录跨机器连接一定要有root%或者干脆单独建一个sqoop%账号。第三步看 JDBC 驱动版本和 URL 参数。MySQL 8.0 之后JDBC 驱动推荐用com.mysql.cj.jdbc.DriverURL 里要加useSSLfalse和allowPublicKeyRetrievaltrue后者是因为 MySQL 8.0 默认使用 caching_sha2_password 认证插件很多老驱动因为拿不到公钥而连接失败。这些步骤虽然从 Sqoop 的角度讲的但换成 Java 程序、Python 程序连不上 MySQL排查思路完全一样。凡是连接问题八成在防火墙和账号权限剩下的两成在驱动参数这是这么多年反复验证过的经验。6. 面试高频考点与几段完整的排错实录6.1 一张表讲清MySQL高频面试考点面试这一块很有必要单独整理因为它是检测你 MySQL 掌握程度的最高效方式。我整理了一份面试官最爱问的核心问题清单每个问题我给一句速记答案和展开思路搞定这些绝大部分 MySQL 相关面试环节你都能撑住。问题核心回答要素事务的 ACID 是什么原子性、一致性、隔离性、持久性分别靠 undo log / 约束 / 锁与 MVCC / redo log 实现隔离级别有哪些读未提交、读已提交、可重复读、串行化MySQL 默认可重复读用 MVCC 实现MVCC 是什么多版本并发控制每行数据有多个版本读操作通过版本链和 ReadView 找可见版本实现非阻塞读索引为什么用 B 树B 树矮、扇出大、磁盘 IO 少、叶子链表适合范围查询对比 B 树和哈希索引什么是回表和覆盖索引二级索引叶子存主键查到主键后再回聚簇索引取整行叫回表索引已包含所有需要的字段叫覆盖索引慢查询怎么排查开启慢查询日志用 EXPLAIN 看 type、key、rows依次排除索引失效、数据量大、锁等待等问题binlog、redo log、undo log的区别binlog 是 Server 层归档日志redo log 是 InnoDB 崩溃恢复日志undo log 是回滚日志主从复制原理主库写 binlog从库 IO 线程拉取 binlog 写入 relay logSQL 线程重放 relay log 完成同步不要死记硬背把这些知识点放到文章前面讲的架构和索引原理里去理解。比如主从复制为什么需要 binlog就是因为 binlog 是 Statement/Row 级别的变更记录天然适合传送到别的实例上重放。理解了日志的本质很多题你现场推都能推出来。6.2 排错实录一一条慢查询从发现到解决的完整链路有一回线上报表系统反馈某条统计查询跑了一分多钟还没出结果。我先用EXPLAIN看了一下执行计划type列明晃晃显示ALLrows估算扫描 500 多万行。表里明明有索引为什么没走我再细看 WHERE 条件发现写的是SELECT * FROM order WHERE DATE(create_time) 2024-01-01 AND status 1;问题出在DATE(create_time)这个函数上。索引列被函数包裹之后B 树的有序性失效优化器只好放弃索引走全表扫描。这种场景有两个改法一是改写为范围查询SELECT * FROM order WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00 AND status 1;二是如果create_time的索引确实用不上了考虑建一个函数索引MySQL 8.0 支持。但函数索引在写操作时有额外开销不是第一选择。我的习惯是先改写 SQL实在没法改再考虑动表结构。这一步小小的改写查询时间从 60 多秒下降到 0.2 秒直观得让人心疼之前的用户体验。6.3 排错实录二MySQL连不上的完整排查顺序另一种高频问题就是“应用连不上 MySQL”包括前面讲的 Sqoop 连接失败也一样。这里总结一套通用的排查链路按顺序走基本都能定位先看报错类型。Access denied是认证问题Connection refused是连接被拒绝Communications link failure多半是网络或驱动参数问题。本机先测在 MySQL 服务器上用mysql -u root -p登录能登录说明实例本身是活的。测端口监听netstat -tlnp | grep 3306看 mysqld 是否监听在 3306。如果只监听了 127.0.0.1而你的应用从别的机器过来永远连不上此时要检查my.ini里的bind-address配置。测跨机器连通性在应用所在机器telnet MySQL服务器IP 3306不通就是防火墙或安全组问题去放行端口。检查账号主机域SELECT user, host FROM mysql.user;确认应用账号是否允许从应用所在 IP 段访问。检查驱动和连接串8.0 用新版驱动和参数尤其注意 SSL 和公钥获取相关参数。这套链路能解决我遇到的 90% 以上连接问题。剩下 10% 基本都是版本兼容、驱动不匹配一类给报错信息一搜就有答案。6.4 我的一个执念每次写完SQL都养成分步验证的习惯最后说点个人体会。MySQL 学习到后期“技术知识”已经很局限了真正拉开差距的是验证习惯。我见过太多人写完一段 SQL 直接怼到生产库执行出问题才看日志。而我的习惯是先在一个备份库或者测试环境把 SQL 拆分验证SELECT不断缩小范围UPDATE之前先跑对应的SELECT看影响行数加索引之后马上EXPLAIN看是否生效。这种方法看着慢实际上是把排查成本前置了。数据库这个东西最怕的不是不会写而是不知道自己写的语句在数据库里经历了什么。当你把连接、日志、索引结构这些底层逻辑串成一条线遇到任何问题你都能顺着这跟线找到答案而不是靠重启和碰运气。MySQL 的学习曲线其实不陡但细节是真的多。你可以把这篇东西当作一张地图安装遇到问题回来翻第一章SQL 写不顺翻第三章慢查询搞不定翻第四章连不上翻第六章。每个板块都能独立看串起来就是一条完整的“入门到入魔”路线。等你有一天自己也能对着 EXPLAIN 结果自信地说出“这条 SQL 应该走哪个索引”你就已经不再是入门选手了。
返回列表