做后端开发这些年,MySQL算是打交道最多的东西之一。很多人一提起“数据库操作”,第一反应就是建库、建表、增删改查,觉得都是背几句SQL就能搞定的事。但真到了生产环境,在库的层面栽过跟头的人都会告诉你,越基础的操作,越容易出事。删库删错、字符集建错导致乱码、大表直接改结构把业务锁死,这几类事故我全见过,有的就在我自己手上。这篇内容,就把MySQL最核心的库操作、表操作、数据操作完整梳理一遍,把每个命令背后的注意事项和我的实操习惯都讲清楚,适合刚入行的同学系统过一遍基础,也适合写了一阵子代码但没认真整理过MySQL细节的开发者查漏补缺。
1. 先搞清楚:MySQL的“数据库操作”到底包含什么
1.1 数据库操作的四层结构
接触MySQL之前,最好先把它的层次结构在脑子里搭出来。一台MySQL服务器从大到小分成四层:最外层是MySQL实例,也就是你通过mysql -u root -p连上的那个服务进程;实例下面才是“数据库”,也叫schema,平时说的“建库”就是在这一层操作;每个数据库里面又有若干张表;表里面才是一行一行的数据。
很多人建库建表很熟练,却说不清这个层级,结果就是操作时定位不清。比如想删除某个业务库的数据,却把整个库直接DROP掉,这在实例、库、表、数据四层里其实是“库”这一层的事故,影响面却是下面所有层——库没了,表没了,数据也就全没了。理解这个层级关系,你才会明白为什么库级操作权限通常收得很紧,也为什么DBA对DROP DATABASE这么敏感。
我认为初学阶段最值得花时间的就是库和表这两层。数据操作(增删改查)写错了最多影响几行,最多再改回来;库和表的结构操作写错了,影响的是整个业务底座。后面说的所有内容,都会围绕这两层展开。
1.2 库级操作最容易被忽视的风险
库级操作的特点是命令少、语法简单,看上去毫无技术含量,但风险往往被严重低估。我见过一个测试环境的库被开发同学连着DROP DATABASE干掉,原因是他在终端里想清空某张表,历史命令翻出来,发现上一条是删除整个库的命令,手一抖就执行了。这种情况在真实团队里一点都不罕见。
库级操作的风险主要来自三个方面:一是不可逆,MySQL没有“回收站”,DROP DATABASE这种操作一旦执行,数据就是物理删除,大多数情况下只能靠备份恢复;二是影响面大,一个库下面可能挂了上百张表,删一个库等于同时删掉所有关联业务的数据;三是操作对象往往是共享资源,多个应用、多个开发同学共用一个库,你一个人的误操作,影响的是整个团队甚至线上用户。
所以后面讲每个命令的时候,我都会把“动手之前要确认什么”放在前面说。这不是形式主义,数据库这个行业里,绝大多数线上事故都是“低级操作+低级疏忽”的组合,而不是技术难度本身。
2. 建库与删库:越简单越要小心
2.1 CREATE DATABASE 的完整写法与参数选择
建库的标准写法是这样的:
CREATE DATABASE IF NOT EXISTS `mall_app` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;先说IF NOT EXISTS。这个关键字不是可有可无的,尤其当你写的脚本需要重复执行时,没有它,第二次运行就会直接报错ERROR 1007 (HY000): Can't create database 'mall_app'; database exists。在自动化部署脚本里,这个报错会导致整个脚本中断。加上IF NOT EXISTS相当于告诉MySQL“存在就算了,别报错”,脚本就具备了幂等性,跑多少次都不会因为它中断。
再说反引号。MySQL里反引号是用来包裹标识符的,包括库名、表名字段名。mall_app本身没问题,但如果你把库名起成了order、group这种和SQL保留字撞车的名字,不带反引号就会直接语法报错。我的习惯是所有库名、表名、字段名一律加反引号,统一习惯,省得以后遇到保留字还要回头改。
然后是最容易被新手忽略的DEFAULT CHARACTER SET和COLLATE。这两个参数决定了这个库默认用哪种字符集和排序规则。不显式指定的话,MySQL会继承实例级配置,而很多默认安装的实例字符集是latin1,存不了中文,更存不了emoji。等表建好了、数据写进去了再改字符集,就要做全表转换,代价完全不一样。
2.2 字符集与排序规则:utf8mb4为什么是默认答案
字符集这块我多说几句,因为坑实在太常见了。在MySQL里,utf8这个字符集其实是个“残缺版”,它最多只支持3字节的UTF-8编码,而真正的UTF-8标准里有4字节的字符,最典型的就是emoji表情。也就是说,如果你的表字符集是utf8,用户昵称里带个😀表情,INSERT语句直接报错或写入乱码。解决办法只有一个:用utf8mb4,这个才支持完整的4字节UTF-8。
排序规则COLLATE也要跟着字符集走。字符集是utf8mb4时,常用的是utf8mb4_general_ci和utf8mb4_unicode_ci。前者排序速度略快,使用的是简化后的排序规则;后者更接近Unicode官方排序标准,对各国语言文字的排序更准确。大多数业务场景这俩差别感知不明显,我通常用utf8mb4_general_ci,省一点性能开销;如果业务对多语言排序准确性要求高,就选utf8mb4_unicode_ci。
还有一个细节:CHARACTER SET只对“新建的表”生效。已经存在的表不会因为库改了字符集就自动转换。你ALTER DATABASE改了库的字符集,老表的字符集还是原来的,需要单独对每张表执行转换。所以最好的策略是一开始就把库、表、字段的字符集统一设置为utf8mb4,宁可一步到位,不要事后补救。
2.3 DROP DATABASE 前必须确认的三件事
DROP DATABASE的语法简单到不能再简单:
DROP DATABASE IF EXISTS `mall_app`;但执行之前,我建议你把下面三件事挨个过一遍。
第一,确认库名。别笑,很多人真的会看错。我的做法是执行前先SHOW DATABASES;看一眼目标库的完整名字,避免mall_app和mall_app_test这种相似名字看混,也避免手滑多打几个字符。条件允许的话,用脚本来做,不要在命令行里手动敲删除语句。
第二,确认备份。生产环境的库在删除前必须有可恢复的备份,通常是用mysqldump把整个库导出成SQL文件。没有备份就没有删除资格,这是一条铁律。即使备份了,恢复也需要时间,线上业务等不起,所以实际操作里生产库很少真的执行DROP DATABASE,更多是“标记废弃+归档”,把库改成只读或者迁走。
第三,确认权限和流程。正规的团队环境里,生产库的DROP权限通常是不给普通开发同学的,要DBA来执行。如果你手里确实有这个权限,更要对操作负责,删除前最好在工单系统里留记录,说明删哪个库、为什么删、备份在哪。这不是劝你走流程,是在保护你自己——线上事故复盘的时候,有记录和没记录,处理结果完全不同。
3. 日常维护:查看、修改与切换数据库
3.1 查看数据库的几种方式与场景
查看实例下有哪些库,用:
SHOW DATABASES;执行结果里除了你自己建的库,还有几个系统自带的库:information_schema、mysql、performance_schema、sys。新手可能会好奇“这些是什么”,简单说,mysql库存的是用户、权限这类系统信息;information_schema是数据库的“元数据字典”,它本身不存业务数据,但提供了所有库、表、字段、索引的视图信息。比如你想快速查一下某个实例下所有库占了多少磁盘空间,就可以查information_schema.tables,按table_schema分组求和data_length + index_length。
想单独看某个库的建库语句和属性,用:
SHOW CREATE DATABASE `mall_app`\G这里\G是MySQL客户端里一个很实用的小技巧,作用是把横向输出变成纵向,字段多的时候看得很清楚,比用分号结尾的横表可读性强很多。进阶一点的用法是SELECT schema_name FROM information_schema.schemata;,效果和SHOW DATABASES一样,好处是它是标准SQL,可以被其他工具或脚本包装复用。
3.2 ALTER DATABASE 修改库属性的边界
改库属性的场景不太多,最常见的就两个:改默认字符集,改排序规则。
ALTER DATABASE `mall_app` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这个命令的“边界”一定要搞清楚:它只修改数据库的默认属性,只影响这个库下“以后新建”的表和字段,已经存在的表不会自动转换字符集。我见过有人执行完这一步,看到老表还是乱码,就以为命令没生效,其实不是,你得对老表单独做一次转换:
ALTER TABLE `users` CONVERT TO CHARACTER SET utf8mb4;需要注意的是,CONVERT TO CHARACTER SET会重写整张表的数据,如果表很大,这个过程会锁表并产生大量IO,要挑业务低峰期操作,最好先用测试环境验证耗时。
3.3 USE 切换数据库的隐形陷阱
USE `mall_app`;这个命令在客户端里几乎是天天用,但它有一个很多人没意识到的性质:USE只对“当前会话”生效。也就是说,你在一个终端窗口里USE mall_app,换一个新开的终端窗口,默认库还是空的,直接执行SELECT * FROM users;会报ERROR 1046 (3D000): No database selected。
这个问题在手动操作时容易发现,但在脚本和定时任务里很容易埋雷。比如一个Python脚本里用连接池管理MySQL连接,某次请求执行了USE,但连接被归还后又被其他请求复用,默认库状态就变得不可预测。我见过一个定时任务,脚本里前面执行了USE A库,后面又执行USE B库,中间某条SQL没带库名前缀,结果数据写到了错误的库里,排查了很久。
稳妥的做法是:在自动化脚本和定时任务里,所有SQL都写完整限定名,比如SELECT * FROM mall_app.users;,完全不依赖USE的状态。USE只用于手动排查问题时减少打字量。
4. 表操作:数据库操作的核心战场
4.1 建表:字段类型、主键与约束的一次性设计
建表是数据库操作里最需要“一次做对”的环节。改字段类型、加约束、补索引,在表还空着的时候很容易,但一旦数据量上来,每次结构变更都是代价不菲的运维动作。先看一个标准的建表语句:
CREATE TABLE IF NOT EXISTS `users` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` VARCHAR(64) NOT NULL COMMENT '用户名', `email` VARCHAR(128) NOT NULL COMMENT '邮箱', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1启用,0禁用', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`), UNIQUE KEY `uk_email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';先说字段类型。整数类型从TINYINT到BIGINT一共5档,选择依据是取值范围。用户ID用BIGINT UNSIGNED是稳妥选择,虽然很多场景INT也够用,但INT上限约21亿,对互联网业务来说没有想象的那么难触达,与其等溢出再改类型,不如一开始就用BIGINT。状态字段用TINYINT就够,0和1而已,用INT是浪费空间。价格金额这种要精确计算的,必须用DECIMAL(10,2),不要用FLOAT或DOUBLE,二进制浮点数的精度误差在金额核算时是会出大问题的。
再说主键。我强烈建议每张表都设一个自增BIGINT主键,理由有几个:InnoDB是索引组织表,数据按主键顺序物理存放,自增主键能保证插入时追加写,减少页分裂;AUTO_INCREMENT主键天然唯一,业务代码里不需要额外的生成逻辑;后续分库分表、数据同步也都依赖主键来做路由和去重。业务上具有唯一性的字段(比如邮箱、手机号)可以再建唯一索引,但不要拿来做主键,一方面字符主键会让辅助索引变得很大,另一方面业务规则的修改会导致主键变更,代价极高。
4.2 ALTER TABLE 修改表结构的风险与替代方案
表结构不是不能改,但要带着风险意识改。常见的ALTER TABLE操作有这么几类:
-- 新增字段 ALTER TABLE `users` ADD COLUMN `avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像' AFTER `email`; -- 删除字段 ALTER TABLE `users` DROP COLUMN `avatar`; -- 修改字段类型或默认值 ALTER TABLE `users` MODIFY COLUMN `email` VARCHAR(200) NOT NULL COMMENT '邮箱'; -- 新增索引 ALTER TABLE `users` ADD INDEX `idx_status` (`status`);每一条背后都有坑。新增字段时,MySQL 5.6之后InnoDB支持了“在线DDL”的一部分能力,但具体是否锁表取决于操作类型。比如ADD COLUMN大部分场景不锁表了,但如果是MODIFY COLUMN改变字段类型,很可能仍然需要重建整表,锁表期间所有写操作都会堵住。ADD INDEX在数据量大的表上同样代价高昂,扫描全表建索引期间会有明显性能抖动。
我的实操建议是:表里的数据量超过千万行之后,对结构做重大变更前,一定要先在测试环境模拟一遍,用EXPLAIN确认不会全表扫描,用低峰期窗口执行。如果业务不能接受锁表,就需要借助在线DDL工具,这类工具通过创建临时表、增量同步数据、切换表名的方式实现平滑变更,原理上类似“影子表”,能大幅降低对线上业务的影响。变更完成后,不要急着删旧表,保留一段时间观察运行情况。
4.3 DROP TABLE 与 TRUNCATE TABLE 的差异
很多新手分不清DROP TABLE、TRUNCATE TABLE和DELETE FROM三者,但它们之间的差异非常大,用错场景会有完全不同的后果。先用一张表说明差异:
| 操作 | 作用范围 | 能否回滚 | 自增ID是否重置 | 是否触发删除触发器 |
|---|---|---|---|---|
| DROP TABLE | 删除表结构和数据 | 不能 | 表都没了 | 不触发 |
| TRUNCATE TABLE | 清空表数据,保留表结构 | 不能(隐式提交) | 重置 | 不触发 |
| DELETE FROM | 按条件删除行 | 可以(受事务控制) | 不重置 | 触发 |
TRUNCATE TABLE users;的语义是“把表清空,但表还留着”,这个过程是直接把表中的数据页释放掉,速度极快,但它是隐式提交,即使在事务里执行也不能回滚。DROP TABLE更彻底,直接释放表定义和数据,磁盘空间也一并归还。
实际工作中,清空一张表之前先确认三件事:这张表是不是真的不需要了;有没有别的表通过外键或业务逻辑引用它;如果是配置类数据,清空后应用能不能自动重建。有一类常见事故是:本想在测试环境TRUNCATE一张临时表,结果连错了实例,把生产环境的一张配置表清空了,整个应用启动后全部配置缺失,服务雪崩。所以任何清空和删除操作,第一步永远是确认当前连接的库和表。
5. 数据操作:增删改查的实践细节
5.1 INSERT:字段列表与批量插入的讲究
数据操作是日常最频繁的,反而更要建立好习惯。先看INSERT。
INSERT INTO `users` (`username`, `email`, `status`) VALUES ('alice', 'alice@example.com', 1);写INSERT时我要求自己永远显式列出字段名。两个好处:一是表结构后续新增字段时,不带字段列表的INSERT INTO users VALUES (...)会立刻报错或错位,而带字段列表的语句不受影响;二是可读性,别人看代码时一眼知道每个值对应哪个字段。
批量插入的性能优势非常明显。同样插入1000行数据,分成1000次单条插入和一条语句插入1000行,后者速度可能是前者的几十倍。批量插入的写法是:
INSERT INTO `users` (`username`, `email`) VALUES ('alice', 'alice@example.com'), ('bob', 'bob@example.com'), ('carol', 'carol@example.com');但要注意,一次插入的行数也不是越大越好。事务太大、生成的binlog太多,会在主从复制时产生延迟。我常用的经验值是单批500行到1000行,具体看字段平均大小,字段多就少一些,字段少就多一些。
重复数据的处理是另一个高频需求。业务上经常会遇到“有则更新,无则插入”的场景,INSERT ... ON DUPLICATE KEY UPDATE就是为这个设计的:
INSERT INTO `users` (`username`, `email`, `status`) VALUES ('alice', 'alice_new@example.com', 1) ON DUPLICATE KEY UPDATE `email` = VALUES(`email`);注意这里的唯一键冲突由表上的唯一索引决定,没有唯一索引,这语句不生效。如果只是想“忽略重复数据,不报错”,可以用INSERT IGNORE。
5.2 UPDATE:没有WHERE就是事故
UPDATE语句是整个增删改查里风险等级最高的一条,因为它的语法太容易“省略WHERE”了。
UPDATE `users` SET `status` = 0;这一条语句,没有WHERE,后果就是:把users表所有行的status都改成0。如果你本意只是改某个用户的状态,这条语句就把整个用户表的状态全部重置了。就算你发现得及时,在没有备份的情况下,恢复也需要逐条核对数据,工作量极其痛苦。
我给自己定过几条死的规矩,写在这里供参考:
- 写UPDATE先写WHERE,再写SET。很多人习惯先写SET再补WHERE,但只要先写WHERE,就不会出现“忘写WHERE”这种事故。
- 执行前先跑一遍同条件SELECT,确认影响范围。比如想执行
UPDATE users SET status = 0 WHERE username = 'alice';,先执行SELECT id, username, status FROM users WHERE username = 'alice';,看返回结果是不是预期的那些行。 - 有条件的话,在事务里执行。先
BEGIN;,再UPDATE,然后SELECT检查影响的行数,确认无误再COMMIT;,发现不对就ROLLBACK;。
还有一个实际经验:大批量UPDATE不要一口气执行,尤其在主从架构下,一条语句更新几十万行,会导致从库长时间延迟。正确做法是分批更新,比如按主键范围每次更新5000行,循环执行。虽然语句条数变多了,但每一批对主库和从库的压力都比较小,整体反而更稳。
5.3 DELETE:数据清理的正确姿势
DELETE和UPDATE一样,必须把WHERE当成安全底线。
DELETE FROM `users` WHERE `id` = 123;没有WHERE的DELETE FROM users;是清空整张表的操作,和TRUNCATE效果类似,但它会逐行记binlog、逐行触发删除逻辑,速度比TRUNCATE慢得多,而且在主从架构下会造成非常大的复制延迟。我不是说DELETE不能用,而是说执行前要清楚自己在干什么。
DELETE的实际风险还有一条:它和UPDATE一样是事务性操作,理论上可以回滚。但生产中我见到的DELETE事故,很多不是“删错了要回滚”,而是“删了一段时间才发现”,这时候早提交了,回滚也来不及。所以我的建议是:删除线上数据前,先把要删除的数据备份到一个“回收表”里。比如这个场景:
-- 先备份 CREATE TABLE `users_deleted_20250101` AS SELECT * FROM `users` WHERE `status` = 0; -- 确认备份表行数和要删除的行数一致 SELECT COUNT(*) FROM `users_deleted_20250101`; -- 再删除 DELETE FROM `users` WHERE `status` = 0;这个习惯在清理历史数据、下线老业务时特别有用。有了备份表,即使删除后业务逻辑出了问题,也能随时把数据插回去。
另外多说一句“软删除”的设计思路。很多业务表并不真的删除数据,而是用一个status或is_deleted字段标记“不可见”。这能绕过DELETE的所有风险,但代价是查询时每条SQL都要带条件WHERE is_deleted = 0,写起来麻烦,也容易漏。同一个团队最好统一规范,要么都用硬删除+备份表,要么都用软删除,混着来最乱。
5.4 SELECT:查询的基本素养
SELECT看起来最简单,但查询习惯好不好,直接影响数据库性能和代码维护成本。
第一条:不要用SELECT *。在业务代码里写SELECT * FROM users,返回的是所有字段,但你实际只用到其中三个,多余的字段白白占用网络带宽和内存,还让代码依赖变得隐式化。更麻烦的是,以后表里加一个特别大的字段(比如长文本),线上代码的性能可能突然下降,而问题根源很难排查到这条查询上。
第二条:控制返回量。线上环境执行SELECT * FROM users;这种无WHERE、无LIMIT的语句,等于让数据库把整张表的数据吐出来。数据量大时,直接把数据库IO打满。开发环境的表只有几行数据感觉不到,生产环境一张表几千万行的时候,这是妥妥的事故查询。
SELECT `id`, `username`, `email` FROM `users` WHERE `status` = 1 ORDER BY `id` DESC LIMIT 20;第三条:学会看执行计划。查询慢的时候,在SQL前面加EXPLAIN,MySQL会告诉你这条语句用了哪个索引、扫了多少行、有没有文件排序。比如EXPLAIN SELECT ... WHERE username = 'alice';,看到type=ALL就说明是全表扫描,需要检查username上有没有索引。这是一个能帮你提前发现问题、而不是等线上炸了再救火的好习惯。
6. 常见报错速查与实践心得
6.1 高频报错对照表
数据库操作中报错是家常便饭,很多报错信息第一次见时很慌,但背后的原因就那几类。整理一张我实践中最高频遇到的报错表:
| 报错信息 | 常见原因 | 解决办法 |
|---|---|---|
| ERROR 1049 (42000): Unknown database | 连接的库名不存在,或没建库就USE | 先SHOW DATABASES确认库名 |
| ERROR 1007 (HY000): Can't create database; database exists | 库已存在,CREATE DATABASE未加IF NOT EXISTS | 建库语句加IF NOT EXISTS |
| ERROR 1008 (HY000): Can't drop database; database doesn't exist | 库不存在就执行DROP | 加IF EXISTS,或先确认库名 |
| ERROR 1046 (3D000): No database selected | 没有USE,SQL也没带库名前缀 | 执行USE,或写完整限定名库.表 |
| ERROR 1064 (42000): SQL syntax error | SQL语法错误 | 检查关键字拼写、保留字是否要加反引号 |
| ERROR 1050 (42S01): Table already exists | 表已存在 | CREATE TABLE加IF NOT EXISTS |
| ERROR 1146 (42S02): Table doesn't exist | 表不存在,或库名/表名拼错 | 确认表名、SHOW TABLES查看 |
| ERROR 1264 (22003): Out of range value | 数值超出字段类型范围 | 检查数据,改用更大类型的字段 |
| ERROR 1366 (HY000): Incorrect string value | 字符集不匹配,存了4字节字符 | 字段/表字符集改utf8mb4 |
这里想单独说一下ERROR 1366,它在MySQL 8.0环境下更常见了,项目里只要有人往utf8或latin1字符集的表里写入中文或emoji,就会触发。看到这个报错不要慌,基本可以确定是字符集配置问题,把对应字段或表的字符集改成utf8mb4就能解决。
6.2 数据库操作的几条实战铁律
把散落在前面的经验汇总一下,是我这几年实实在在用“教训”换来的几条铁律。
第一条:所有危险操作前先备份。建表前想清楚字段,改表前在测试环境验证,删除前备份,更新前先SELECT确认。很多人觉得“我只是在测试环境操作”,但测试环境往往和生产环境在同一个实例上,连接串写错一个字母,操作的就是生产库。“测试环境随意操作”这个心态本身就是事故源头。
第二条:SQL脚本要留痕,不要“开个终端就敲”。正规项目的库结构变更都应该有脚本文件,命名带日期和变更说明,提交到代码仓库。好处很多:可以review、可以复盘、可以追溯。我见过一个项目,数据库结构全靠某位同事的电脑终端操作,后来同事离职,没人知道某张表的某个字段是什么时候加的、为什么加的,整个团队只能靠猜。
第三条:命名规范统一。库名、表名、字段名统一用小写字母+下划线,不要用驼峰,不要用中文,不要用大小写混合。为什么?MySQL在Linux下对表名大小写敏感,在Windows下不敏感,同一个项目换台机器,大小写混写的表名可能直接解析失败。统一小写+下划线能避免这种环境差异带来的坑。注释更要写清楚,建表语句里给每个字段加COMMENT,过半年回来接手的人会感谢你。
第四条:注重权限最小化。普通开发账号不应该有生产库的DROP、TRUNCATE权限,也不应该有其他业务库的写权限。权限越大,出事故时的破坏半径就越大。如果你发现自己手上权限过大,主动收敛不是坏事。
6.3 最后聊几句我的个人体会
写到这里,把自己这些年和MySQL库操作、表操作打交道的经历也翻出来想了想。刚入行的时候,我也觉得这些基础操作没什么好学的,花了很多精力去研究缓存、消息队列这类看起来更“高级”的东西。后来线上出了几次不算大但足够惊险的事故,才慢慢明白,数据库这块,决定系统稳定性的往往不是那些花哨技术,而是最基础的操作有没有被认真对待。
有一句话说得很到位:数据库本身很可靠,不可靠的是操作数据库的人。建库建表、增删改查,每一个命令都有它该有的敬畏。特别是当你不确定一条语句执行后会发生什么的时候,宁可多花几分钟确认,也不要抱着“应该没问题吧”的心态直接回车。一个负责任的开发者,在把任何SQL语句发到数据库之前,都应该清楚它会改哪些数据、影响哪些表、是否需要备份、能否回滚。
如果你现在刚开始学MySQL,或者已经写了一阵子代码但对库表层面的操作还没形成系统认知,希望这篇内容能帮你少走一些弯路。数据库操作的容错空间其实很小,但好消息是,只要养成“先确认再执行、先备份再删除、先测试再上线”的习惯,绝大多数事故都是可以避免的。以后我会继续写一些表设计、索引优化、慢查询排查方面的实操内容,把这些年在数据库上踩过的坑,一个一个填平。