不用报任何班,也不用啃完一本几百页的书。学 MySQL,先把“数据库基础”这四个字吃透,后面所有的高并发方案、读写分离、性能调优才有落地的底气。我见过太多人一上来就装环境、建表、写查询,结果一问存储引擎有什么区别、事务隔离级别到底隔离了什么、为什么这个查询明明有索引却还是慢,整个人就卡住了。
这篇就把我这些年踩过的坑和带新人总结出来的经验一次讲清楚。不管你是刚接触数据库的纯小白,还是被公司里线上问题逼着补课的半路出家选手,只要能花半天时间把这些基础环节过一遍,再看复杂的项目案例,思路会顺很多。
1. 学 MySQL 之前,先把这三件事想明白
1.1 为什么是 MySQL,而不是 Oracle 或 PostgreSQL
很多新人喜欢问“学哪个数据库好”,我的建议是:如果是为了就业和日常开发,MySQL 是第一选择。原因很简单,市面上绝大多数互联网公司的业务系统、Java 项目、Python 项目,默认的数据库就是 MySQL。你去看招聘要求,几乎每一条后端岗都写着“熟练掌握 MySQL”,这个“熟练”不是指会装、会建表,而是指你理解它的底层逻辑,能处理真实问题。
但这不意味着 MySQL 在所有场景都最优。Oracle 在银行、电信这类对事务和稳定性要求极高的传统行业还是主流,PostgreSQL 在复杂查询和地理信息处理上有天然优势。可对于大多数人的职业起步来说,MySQL 的社区资料最全、问题最容易搜到、生态工具也最成熟。
我给你的建议是:入门认准 MySQL,把它学透,之后再看其他数据库会非常快。数据库的核心概念——表、索引、事务、锁——都是相通的,差的只是语法细节和引擎实现。
1.2 基础阶段必须啃下来的知识地图
学 MySQL 最忌讳的就是“东学一块西学一块”。今天搜到一条命令就敲一下,明天看到一个函数就试一下,看起来每天都在学,实际上脑子里的知识是散的。我整理过一份基础路线,按这个顺序走,基本不会迷路:
- 库和表的概念:什么是数据库、数据表、字段、记录,这些是最基本的物理单位。
- SQL 分类:DDL(数据定义)、DML(数据操作)、DCL(数据控制)。很多人分不清这些,后面才会出现“怎么删不掉数据”“怎么权限不对”的疑问。
- 数据类型:整数、小数、字符串、日期时间、枚举等,选错类型会吃大亏,这个后面细讲。
- 约束与索引:主键、外键、唯一约束、普通索引、联合索引,这是查询性能的根基。
- 事务与锁:ACID 特性、隔离级别、锁的分类。基础中的核心重点。
- 常用函数与聚合:排序、分组、条件统计,掌握这些就能覆盖日常开发 90% 的查询需求。
把这六块吃透,再去看存储过程、视图、触发器、分区表这些进阶内容,就只是时间问题了。
1.3 存储引擎选型,别只在 InnoDB 和 MyISAM 之间纠结
很多新手建表的时候根本没关注过存储引擎,直接用了默认配置。在 MySQL 5.5 之前的默认引擎是 MyISAM,5.5 之后改成了 InnoDB,现在用的 5.7 和 8.0 版本默认都是 InnoDB。这里面有一个非常关键的认知:默认引擎已经帮你把最重要的选择做了,如果你非要去手动改,必须清楚自己在干什么。
我做一个最直观的对比表,方便你理解两者差异:
| 对比项 | InnoDB | MyISAM |
|---|---|---|
| 事务支持 | 支持 ACID | 不支持事务 |
| 外键约束 | 支持 | 不支持 |
| 锁粒度 | 行级锁 | 表级锁 |
| 崩溃恢复 | 支持,有 redo log | 不支持,崩溃可能丢数据 |
| 全文索引 | 5.6 后才支持 | 原生支持 |
| 适用场景 | 大部分业务系统 | 只读报表、日志分析类 |
为什么默认是 InnoDB?因为对于绝大多数业务来说,数据可靠性和并发能力比那点查询性能更重要。MyISAM 在纯读场景下确实更快,但它一个表级锁就注定了并发写入会排队,一条慢查询能把整张表堵死。我自己接手过一个遗留系统,里面的报表表用了 MyISAM,业务量一上来就锁表,后来全部迁到 InnoDB 才稳定。
所以,除非你有非常明确且充足的理由,否则一律用 InnoDB。这不是偷懒,是在用前人的经验避坑。
2. 环境搭建:Windows 安装 MySQL 5.7 和 8.0 的实操细节
2.1 下载版本选择:5.7.44 与 8.0 到底怎么选
热词里高频出现的“mysql 5.7.44 官方为什么之后 5.7.43 呢”,很多人没搞懂这个版本号的含义。简单解释一下:5.7.44 属于 5.7 系列的补丁版本,官方对 5.7 系列的维护一直持续到 2023 年才结束 EOL,所以 5.7.43 之后继续推送了 5.7.44 作为最后的收官版本之一,这很正常,用不着多虑。
至于选 5.7 还是 8.0,我的建议是:
- 新项目、新学习:直接上 8.0。8.0 的默认字符集已经是 utf8mb4,窗口函数、CTE 公共表表达式这些功能非常实用,性能也有明显提升。
- 接手老旧项目:跟着线上版本走,绝大多数老项目跑的是 5.7,你没有必要在生产环境冒险升级。
- 学习兼就业:优先 8.0,因为现在新公司建库基本都是 8.0 起步,你学新的不亏。
2.2 Windows 下 MySQL 8.0 解压版详细安装流程
Windows 上装 MySQL 有两条路:一个是下载安装版 exe 一路点下一步,一个是下载解压版自己配置。如果你以后要跟 Linux 服务器打交道,我强烈建议你用解压版走一遍手动配置流程,因为这种“自己动手改配置文件”的方式能帮你把原理搞清楚,以后在服务器上哪怕没有可视化界面也能搞定。
第一步,去官网下载 mysql-8.0.x-winx64.zip 包,解压到一个干净路径,比如 D:\tool\mysql-8.0.46-winx64。注意路径里不要有中文和空格,不然以后启动服务可能报各种奇怪的错。
第二步,在解压目录下新建 my.ini 配置文件,这是关键一步,内容模板我直接给你:
[mysqld] # 端口号 port=3306 # 安装目录,改成你自己的实际路径 basedir=D:/tool/mysql-8.0.46-winx64 # 数据存放目录,需要提前建一个空文件夹 datadir=D:/tool/mysql-8.0.46-winx64/data # 字符集 character-set-server=utf8mb4 # 默认存储引擎 default-storage-engine=InnoDB # 跳过密码验证,首次登录重置密码用 # skip-grant-tables [client] default-character-set=utf8mb4第三步,用管理员身份打开命令行窗口,进入解压目录的 bin 目录,执行初始化命令:
mysqld --initialize-insecure这里我特别说明一下:加了--initialize-insecure参数,初始 root 用户是没有密码的,方便你第一次登录后立刻改密码。如果你用--initialize,系统会随机生成一个临时密码写进日志文件,你得去 data 目录找 .err 文件翻,麻烦得很。
第四步,安装 Windows 服务并启动:
mysqld --install MySQL net start MySQL启动成功的标志是命令行提示“MySQL 服务正在启动 . MySQL 服务已经启动成功”,如果卡在“正在启动”就没下文了,大概率是 my.ini 里的路径写错或者 datadir 目录权限有问题。
第五步,登录并改密码:
mysql -uroot -p因为初始化时没设置密码,提示输入密码时直接回车就行。登录后执行:
ALTER USER 'root'@'localhost' IDENTIFIED BY '你的新密码'; FLUSH PRIVILEGES;2.3 用 Docker 装 MySQL,省心还是添乱
热词里有大量“docker 安装 mysql”“docker compose 部署 mysql”“docker 离线安装 arm 架构 mysql”的搜索,说明容器化部署已经成为日常了。Docker 装 MySQL 最大的好处是隔离环境,不用在宿主机上留下一堆依赖,换版本也方便,删容器重跑一条命令搞定。
一条最基础的命令:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ mysql:8.0但新手用 Docker 最容易踩的坑有三个:
第一个坑是数据丢失。容器默认把数据写在可写层,一旦容器删除数据跟着没了。正确做法是挂载数据卷:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -v /my/own/datadir:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ mysql:8.0第二个坑是版本不一致。本地客户端是 5.x 的,容器里跑的是 8.0,连接时会报认证插件不兼容的错误“Authentication plugin 'caching_sha2_password' cannot be loaded”。这个错误几乎每个从 5.7 迁到 8.0 的人都会遇到,因为 8.0 默认的认证插件变了。解决方式是用 mysql_native_password 重建用户的认证方式,或者干脆客户端也升级到 8.x。
第三个坑是 docker pull 失败。热词里的“failed to decode referrers index: invalid”就是这么来的,这多半是你本机 Docker 版本太低或者镜像源连不上导致的。升级 Docker Desktop、换一个可用的镜像源,基本都能解决。
2.4 破解 MySQL 忘记密码的几种方式
这是个高频问题,我单独说一下。忘记 root 密码,在 Windows 端最常用的方式是改 my.ini,往[mysqld]段加上一行skip-grant-tables,重启 MySQL 服务,此时所有客户端免密登录。登录后把密码改掉,再把那行注释掉并重启服务,恢复正常。
在 Docker 环境就更简单了,直接进入容器内部改配置或者用环境变量重置:
docker exec -it mysql8 mysql -uroot -p关键是记住一个原则:任何时候修改认证相关的配置前,先备份数据。别问我怎么知道的,问就是见过太多改配置改崩了然后把 data 目录当垃圾清的惨案。
3. 数据库基础核心:建库、建表、字段设计
3.1 规范化设计,从区分实体和字段开始
很多初学者拿到需求直接开建表,结果后期各种冗余、冲突、改不动。我自己带人时总是先讲一句话:先把现实世界的对象抽象成实体,每个实体对应一张表,实体之间有关系的用外键关联,而不是把所有东西塞进一张大表里。
举个例子,做一个学生课程成绩系统。普通新人可能直接建一张表:
CREATE TABLE student_score ( id INT PRIMARY KEY AUTO_INCREMENT, student_name VARCHAR(50), student_class VARCHAR(50), course_name VARCHAR(50), score INT );看起来没毛病,但你要是存上 1000 个学生、每人选 5 门课,这条表里会出现大量重复的学生姓名和班级信息,改个班级名得 UPDATE 一堆行。正确的做法是拆分成三张表:学生表、课程表、成绩表。
学生表存学生的基本稳定信息,课程表存课程的基本信息,成绩表只存学生 id、课程 id 和分数这三个动态关联字段。这就是第一范式到第三范式的基本思想,核心目标就是减少数据冗余、保证一致性。
3.2 字段类型选择:这些对比直接决定表的性能上限
字段类型的选择一眼看上去太基础了,但实际影响很大。我总结几个最常见的对比,都是实际开发中反反复复踩的坑。
整数类型
| 类型 | 存储空间 | 范围 |
|---|---|---|
| TINYINT | 1 字节 | -128~127 |
| SMALLINT | 2 字节 | -32768~32767 |
| INT | 4 字节 | -21亿~21亿 |
| BIGINT | 8 字节 | 极大范围 |
选类型的一个原则是够用就好。存年龄用 TINYINT 就够了,你却用 BIGINT,浪费 7 个字节;一张表几十个字段、几千万行,这个浪费就大了。反过来,存订单金额这种可能超过 21 亿的值,用 INT 会溢出导致数据异常。
小数类型
一个经典的坑:金额字段用 FLOAT 或 DOUBLE 存。FLOAT 和 DOUBLE 是浮点数,会有精度丢失问题,0.1 加 0.2 在二进制世界里不是精确等于 0.3 的。做金融类项目,金额必须用 DECIMAL,它是以字符串形式存储的精确小数,比如DECIMAL(10,2)表示最长 10 位、小数占 2 位。
字符串类型
CHAR 是定长字符串,VARCHAR 是变长字符串。怎么选?一个非常实用的经验:长度不太会变的用 CHAR,比如手机号、身份证号;长度变化大的用 VARCHAR,比如用户昵称、文章标题。CHAR 的查询性能略优于 VARCHAR,因为它是定长的,磁盘存储位置可以精确计算,但你要是把长度差异很大的文本存进 CHAR,空格填充的浪费会让你哭着改表。
3.3 主键设计的几个硬性建议
主键这个东西,新人觉得不就是加个 PRIMARY KEY 吗,没什么好学的。但主键设计不好,后期索引分裂和性能问题会非常明显。
我的建议是:使用自增 INT/BIGINT 作为主键,除非有极强的业务要求,不要用业务字段当主键。原因有两点:
第一,自增主键写入时是有序的,B+ 树索引的叶子节点会顺序写入,能减少页分裂。UUID 作为主键是随机字符串,插入时索引页会频繁分裂,造成性能下降和碎片空间。在一张亿万级的表里,这个差异是肉眼可见的。
第二,业务字段做主键的风险在于业务会变。比如用手机号做主键,哪天业务支持多账号体系了,手机号变成了可变更字段,你改主键比改数据难一万倍。自增主键是纯物理标识,跟业务彻底解耦,永远不用担心这种问题。
3.4 常用 DML 语句实操:增删改查的避坑写法
数据操作的核心语句其实就四类:INSERT、UPDATE、DELETE、SELECT。但我说几个很多人忽略的细节,都是真实开发中容易出问题的地方。
INSERT 插入时,如果你插入的数据量很大,建议用批量插入而不是逐条插入,比如:
INSERT INTO t_user (name, age) VALUES ('张三', 18), ('李四', 20), ('王五', 22);逐条插入会产生大量的日志和网络往返,批量插入能显著提升效率。在线上做数据迁移时,这个优化经常能把几小时的任务缩短到十几分钟。
UPDATE 没加 WHERE 条件会把整张表的数据都改了,这是事故级错误。我个人的防御习惯是:执行 UPDATE 和 DELETE 前,先 SELECT 一遍确认 WHERE 条件筛选出的数据是要操作的那批,再用同样的条件去执行更新或删除。
DELETE 后想找人恢复数据?几乎不可能。哪怕开启了 binlog,恢复也是一场噩梦。所以养成习惯:删除前先备份,尤其是生产环境的表。备份语句很简单:
mysqldump -uroot -p 数据库名 表名 > 备份文件.sql4. 索引与排序:查询慢的根源和解决思路
4.1 索引为什么快:从全表扫描到 B+ 树查找
很多人不理解为什么建了索引查询就快了。我用生活化的方式讲一下。
没有索引时,MySQL 要查某条数据,只能把整张表从头到尾扫一遍,对应到磁盘上就是每一行都读出来比对,这叫全表扫描。假设表里有 1000 万行,平均查一次要读几百万行数据。有索引后,MySQL 维护了一棵 B+ 树,树的每个节点存放索引值和对应数据行的指针。查数据时沿着树往下找,就好比在字典里查字,不需要从第一页翻到最后一页,直接按拼音或偏旁索引定位,查找次数是树的高度,通常也就三层到四层。
这个差距有多大?全表扫描可能是秒级甚至分钟级,走主键索引查找通常是毫秒级。线上接口卡死、数据库 CPU 打满,八成就是某个关键查询没有走索引,在做全表扫描。
4.2 常见索引类型和创建方式
MySQL 中常见的索引有三种分类:
- 普通索引:最基本的索引,没有唯一性限制,只为加速查询。
- 唯一索引:索引列的值必须唯一,适合手机号、身份证等字段。
- 联合索引:多个字段组合成一个索引,遵循最左前缀原则。
创建索引的语法:
CREATE INDEX idx_name ON t_user(name); CREATE UNIQUE INDEX idx_phone ON t_user(phone); CREATE INDEX idx_class_score ON t_student(class_id, score);这里特别说一下联合索引的最左前缀原则。联合索引 (class_id, score) 本质上先按 class_id 排序,再按 score 排序。如果你查询条件是WHERE score = 90,这个索引用不上,因为 order by 的第一个排序键 class_id 缺失了。你必须把 class_id 放在条件里,WHERE class_id = 1 AND score = 90才能命中索引。
所以设计联合索引时,字段顺序不能拍脑袋,要把区分度高的、查询频率高的字段放在前面。
4.3 排序也走索引,别乱加 ORDER BY
热搜词里有“mysql排序”,排序在实现上分为文件排序和索引排序两种。文件排序是指 MySQL 把查出来的数据先放到内存或磁盘上排序,当排序的数据量大且超过 sort_buffer 阈值时,会落盘成多个临时文件再归并排序,性能非常差。
最理想的排序方式是让排序走索引。因为你建联合索引时,索引本身就是按顺序排列的,MySQL 直接从索引里读出来就是有序的,根本不需要额外排序。比如我建了索引 (class_id, score),执行SELECT * FROM t_student WHERE class_id = 1 ORDER BY score,MySQL 会直接从索引中按顺序读取数据。
反过来说,如果排序字段没有索引,或者排序方向和索引方向不一致,MySQL 就只能文件排序。所以看到慢 SQL 里出现 Using filesort 这个关键词,第一反应就是检查排序字段是否在索引中。
4.4 索引失效的几类真实场景
有索引并不代表一定能用到索引。以下几种情况是我排查慢 SQL 时遇到最频繁的:
- 对索引列使用了函数:比如
WHERE YEAR(create_time) = 2024,这个写法会让索引失效,因为需要对每一行先算函数再比较。正确写法是WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'。 - 隐式类型转换:有一个 varchar 类型的字段 phone,你写
WHERE phone = 13800138000,数字是整数,MySQL 会把 varchar 转成数字再比较,索引失效。 - LIKE 前导通配符:
WHERE name LIKE '%张%',因为你要匹配任意位置,B+ 树没法从固定位置开始查。但WHERE name LIKE '张%'是可以走索引的,因为开头已知。 - 联合索引未满足最左前缀:前面已经讲过,综合起来一句话,联合索引的查询条件里必须命中第一个字段,否则整个索引都用不上。
每次遇到慢查询,我习惯先去执行EXPLAIN命令看执行计划,查看 type 字段和 possible_keys、key 字段,确认到底走了哪个索引,有没有全表扫描。EXPLAIN 是排查性能问题的第一利器,后面接 SELECT 语句,它能告诉你 MySQL 实际会怎么执行这条查询。
5. 事务、锁与隔离级别:数据库并发稳定性的基石
5.1 事务的 ACID 特性,用转账场景一次吃透
事务是数据库里最核心的概念之一。什么叫事务?一句话概括:一组操作要么全部成功,要么全部失败,不允许出现“做了一半”的状态。
ACID 四个特性,用银行转账来讲最直观。假设你给朋友转 100 块钱,数据库要做两步:扣你的余额、加朋友的余额。
- 原子性(Atomicity):扣款和加款必须作为一个整体。如果扣完款在加款前系统崩了,转账操作就回滚,钱回到你的账户。不能出现钱扣了但对方没收到的情况。
- 一致性(Consistency):转账前后,所有相关账户的总金额不变。系统任何时候都不允许出现数据逻辑错误,比如总账对不上。
- 隔离性(Isolation):转账过程中,其他事务不能看到中间状态。你在转账进行中查询余额,要么看到转账前,要么看到转账后,不能看到你钱被扣了但朋友还没收到的中间状态。
- 持久性(Durability):事务提交后,结果永久保存。即使立刻断电、系统重启,数据也不会丢失。
这四个特性是数据库厂商对用户的基本承诺,不是 MySQL 独有的,Oracle、PostgreSQL 也都遵循。
5.2 事务隔离级别详解:脏读、不可重复读、幻读
隔离性不是完全隔离,而是有程度之分的。MySQL 的默认隔离级别是 REPEATABLE READ(可重复读),这个选择背后有非常实际的原因。
SQL 标准定义了四个隔离级别,从低到高:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能发生 | 可能发生 | 可能发生 |
| READ COMMITTED | 不会 | 可能发生 | 可能发生 |
| REPEATABLE READ | 不会 | 不会 | 可能发生(InnoDB 通过 MVCC 已解决) |
| SERIALIZABLE | 不会 | 不会 | 不会 |
用大白话解释这三个异常:
- 脏读:事务 A 修改了一条数据但还没提交,事务 B 读到了这个未提交的修改。如果 A 回滚了,B 读到的就是一条不存在的数据。
- 不可重复读:事务 A 内两次读同一行,第一次读到的是 100,第二次读到的是 90,因为事务 B 在这期间提交了修改。重点在于同一行数据发生了变化。
- 幻读:事务 A 内两次查询同一范围的数据,第一次查出 2 条,第二次查出 3 条,因为事务 B 在这期间插入了新记录。重点在于数据条数发生了变化。
MySQL 默认的 REPEATABLE READ 级别解决不可重复读的方式是 MVCC(多版本并发控制),它让事务读取数据时基于快照,不会感知到其他事务已提交的修改。
至于幻读,MySQL 的 InnoDB 引擎在 REPEATABLE READ 下通过对范围加间隙锁基本解决了,所以实际开发中你很少遇到幻读问题。这也是为什么 MySQL 官方敢把默认级别设为 REPEATABLE READ,而 Oracle 的默认是 READ COMMITTED。
5.3 锁的分类:行锁、表锁、间隙锁到底锁住了什么
热词里高频出现“mysql锁的分类”“mysql锁表”,我在这里把锁的体系梳理清楚。锁的本质是为了解决并发冲突,MySQL 的锁主要分为以下几类:
按粒度分
- 表级锁:锁住整张表,MyISAM 引擎只用这种锁。效率低,一旦写操作锁表,其他所有读写都会被阻塞。InnoDB 在特殊场景下也会用表锁,比如 DDL 修改表结构时。
- 行级锁:InnoDB 独有,只锁住操作涉及的行,并发性能好,但管理开销比表锁大。
按类型分
- 共享锁(S 锁):也叫读锁。多个事务可以同时加共享锁读同一行数据,互相不阻塞,但不允许其他事务修改该行。
- 排他锁(X 锁):也叫写锁。一个事务加了排他锁后,其他事务既不能读也不能写该行,直到锁被释放。
这两种锁的兼容关系是:共享锁之间兼容,共享锁和排他锁不兼容,排他锁之间不兼容。
按实现机制分
- 记录锁:锁的是索引记录本身。
- 间隙锁:锁的是索引记录之间的间隙,防止其他事务在间隙中插入数据。这个机制就是解决幻读的核心手段。
- 临键锁:记录锁和间隙锁的组合,InnoDB 默认加锁单位。
我举个例子,一个表里有 id 为 1、2、3 的几条记录,你在 REPEATABLE READ 级别执行SELECT * FROM t WHERE id BETWEEN 1 AND 2 FOR UPDATE,InnoDB 不只会锁 id=1 和 id=2 这两行,还会锁住 (1,2) 之间的间隙和 (2,3) 之间的间隙。目的就是防止其他事务往 1 和 2 之间插入 id=1.5 的记录。
5.4 死锁是怎么发生的,如何避免
两个事务各自持有了一把锁,同时等着对方手里的锁,谁也不让,结果就死锁了。MySQL 检测到死锁会自动回滚其中一个事务,释放它的锁,让另一个事务继续执行,所以死锁一般不会让数据库直接挂掉,但被回滚的那个事务业务上可能就报错了。
真实场景里的例子:事务 A 先更新 id=1 的记录,再更新 id=2 的记录;事务 B 先更新 id=2 的记录,再更新 id=1 的记录。两个事务互相等对方的第二把锁,就是典型的死锁。
避免死锁最有效的办法是:所有事务都按相同的顺序访问资源。比如全公司统一约定,涉及多行更新时先按 id 从小到大排序,再依次更新。这样 A 和 B 都会先锁 id=1,再锁 id=2,只有一个人能成功走到第二步,另一个人会阻塞,死锁就破解了。
还有一个小技巧是,更新多行时尽量缩小锁定范围。范围越大,锁定的行越多,死锁概率越高。能走主键更新的,就不要用范围条件。
6. MySQL 常用命令与运维实用操作
6.1 高频管理命令整理,关键时刻全靠手速
有些命令平时用不到,但出了问题必须要手快。我把最常用的一批整理成速查表:
| 场景 | 命令 |
|---|---|
| 登录数据库 | mysql -uroot -p |
| 查看所有数据库 | SHOW DATABASES; |
| 切换到某个库 | USE 数据库名; |
| 查看库下所有表 | SHOW TABLES; |
| 查看表结构 | DESC 表名; |
| 查看建表语句 | SHOW CREATE TABLE 表名; |
| 备份整个库 | mysqldump -uroot -p 库名 > 备份.sql |
| 恢复整个库 | mysql -uroot -p 库名 < 备份.sql |
| 查看当前正在执行的慢查询 | SHOW PROCESSLIST; |
| 查看数据库版本 | SELECT VERSION(); |
其中SHOW PROCESSLIST是排查线上数据库卡顿的第一手信息。它能显示当前所有连接正在执行的 SQL,如果发现某个连接执行时间特别长、状态是 Locked,基本可以判断是锁等待问题。这时候用KILL 连接ID杀掉对应会话,就能快速解除僵局。
6.2 执行 SQL 脚本与日常备份恢复
“mysql执行sql脚本”是热词,这个需求在项目上线和测试环境初始化时特别常见。执行脚本的方式有三种:
第一种,在 mysql 命令行内执行:
mysql> source /path/to/file.sql;第二种,在系统命令行直接重定向:
mysql -uroot -p 数据库名 < file.sql第三种,进入 mysql 命令行后用\.命令,跟 source 等价。
日常备份我用得最频繁的是 mysqldump,注意这个工具是独立于 mysql 客户端的,需要在 bin 目录下执行。备份时如果库很大,可以加上--single-transaction参数,这样在 InnoDB 引擎下备份不会锁表,不影响线上业务继续读写。
恢复时有一个很容易被忽略的坑:恢复前先建好数据库。mysqldump 备份的文件默认不包含 CREATE DATABASE 语句(除非你备份时加了--databases参数),恢复时目标是哪个库就先用CREATE DATABASE建好。
6.3 线上误操作快速应对:Update 忘带条件怎么办
这是一个几乎每个 DBA 都经历过的噩梦。UPDATE 语句忘加 WHERE 条件或者条件写错了,全表数据被改。遇到这种情况,第一反应不是慌,而是冷静评估损失。
如果 binlog 开启,并且记录格式是 ROW 模式,你可以用 binlog 解析工具把误操作前的数据提取出来恢复。MySQL 8.0 自带 mysqlbinlog 工具,大致流程是:
# 找到最新的 binlog 文件 SHOW BINARY LOGS; # 用 mysqlbinlog 解析,定位到误操作的 SQL 位置 mysqlbinlog --no-defaults /var/lib/mysql/binlog.000001 # 把误操作前后的 SQL 提取出来,找到事务起始点,反向构造恢复语句如果没有开 binlog,那就只能依赖备份库或者从库数据来恢复了,所以我在前面反复强调备份的重要性不是吓唬人。一个最简单的兜底策略:每天定时全量备份,每两小时做增量备份。宁可备份占点磁盘,也好过数据没了干瞪眼。
7. 常见问题与排查技巧实录
7.1 Windows 安装和启动类问题速查
我把热词里出现的 Windows 安装问题,以及我这些年遇到的典型启动错误整理成一张速查表:
| 报错或问题 | 原因 | 解决方案 |
|---|---|---|
| 服务启动后立即停止 | my.ini 路径错误或 datadir 路径不存在 | 检查 basedir 和 datadir 路径是否真实存在,不能用反斜杠结尾 |
| 1067 系统错误 | 配置文件项格式有问题 | 检查 my.ini 是否保存为 ANSI 编码,不要用记事本另存为 UTF-8 |
| 1045 access denied | 密码错误或认证插件问题 | 用 skip-grant-tables 重置密码;8.0 注意认证插件 |
| 2003 can't connect | 服务没启动或端口被防火墙拦截 | net start mysql启动服务,确认 3306 端口未被占用 |
| 10061 connect error | 服务未启动 | 检查服务列表,确认 MySQL 服务状态为 Running |
启动类问题的排查思路永远是先看错误日志,Windows 上日志在数据目录下的 .err 文件里。很多人遇到问题第一反应是去百度搜,搜半天没结果,其实打开 .err 文件一看,第一行就写着什么原因,比自己瞎折腾高效得多。
7.2 SQL 执行效率问题:EXPLAIN 命令怎么读
EXPLAIN 输出的内容里,新手最容易看的是 type 字段,这个字段直接告诉你 MySQL 用了什么方式去查数据:
- const/system:最好,按主键或唯一索引查询,最多返回一行。
- ref:很好,用了非唯一索引,返回匹配某条件的多行。
- range:还行,通过索引范围扫描,
BETWEEN、>、<这类就是 range。 - index:不太好,全索引扫描,等于把整个索引树读了一遍。
- ALL:最差,全表扫描,数据量一大基本就是灾难。
另外还要看 Extra 列,如果出现 Using filesort 或者 Using temporary,说明查询存在排序或分组临时表操作,这是性能杀手。优化方向是调整索引,让排序和分组尽量走索引完成。
我的一个实操模板:每次写完一条重要 SQL,都习惯性跑一下 EXPLAIN,确认 type 不是 ALL,Extra 里没有 Using filesort。养成这个习惯后,线上慢查询基本能少一半。
7.3 MySQL 8.0 的 SSL 连接错误排查
热词里有“mysql ssl连接错误”,这类问题在启用 SSL 加密连接后容易出现。8.0 默认是开启 SSL 支持的,所以客户端连接时如果强制要求 SSL 但证书又不匹配,就会报错。
排查思路分几步:先确认客户端连接时是否加了--ssl-mode=REQUIRED参数,如果加了,检查服务端证书是否有效。如果客户端和服务端都在内网环境,数据链路上没有风险,可以直接用非 SSL 方式连接,在连接串里加ssl-mode=DISABLED。
另外一个常见原因是 JDBC 连接串里写了useSSL=true但没有配置证书,报错内容是Public Key Retrieval is not allowed。8.0 的 connector 对安全要求更高,解决方法有两个:一个是 URL 上加上allowPublicKeyRetrieval=true,另一个是把 useSSL 改成 false(仅限测试环境)。
7.4 高并发场景下的数据库基础保障
看到热词里的“mysql高并发解决方案”,我要泼一盆冷水:高并发不是数据库基础能直接解决的,但如果你基础不牢,连排查问题的资格都没有。
真实的高并发方案层层递进:先从 SQL 层做优化,保证每条语句都走索引、没有全表扫描;再引入缓存层,把热点数据从 MySQL 中扛到 Redis;然后做读写分离,把查询压力分摊到从库;最后才考虑分库分表,把一个库拆成多个库,把一张大表拆成多张小表。
但所有这些方案都建立在同一个地基上:你怎么建的表、你怎么设计的索引、你的事务隔离级别和锁用的对不对。地基没打好,上面盖什么都会塌。这也是我反复强调必须先搞懂数据库基础的原因。
8. 最后分享两个实用习惯
先说第一个习惯:写任何 SQL 前先想清楚这四条。一是涉及的字段有没有索引,二是查询有没有必要的 WHERE 条件,三是能不能避免 SELECT *,四是事务范围能不能缩小。这四条想清楚了,你的 SQL 大概率不会太烂。尤其是 SELECT *,在表字段多、数据量大时,会白白多查很多不需要的数据,拖慢网络和内存开销。业务代码里尽量只查需要的字段。
第二个习惯:任何配置变更、表结构变更、数据批量操作之前,先备份,再操作。哪怕只是改一个字段名,也要把原表结构导出来放在一边。这个习惯看起来笨,但能在关键时刻救命。我在本地环境作图时也坚持这个习惯,因为“顺手改一下”的操作失误率远比你想象得高。
学 MySQL 这事确实没有捷径,但也不需要把它当成一座翻不过去的山。把本文中提到的库表设计、索引原理、事务与锁、常用命令这些基础逐一吃透,多动手敲、多 EXPLAIN、多备份,你很快就会发现自己看问题的方式从“这个功能怎么写”变成了“这个功能在数据库层面到底是怎么跑的”。这一步迈过去,MySQL 就真正入门了。