MySQL常用指令全解析:从建库到备份的避坑实践指南

发布时间:2026/10/17 4:47:58
MySQL常用指令全解析:从建库到备份的避坑实践指南
做后端开发和数据库运维这些年MySQL的指令翻来覆去就是那几十条高频的。我见过不少新手对着官方文档不知所措也见过老手偶尔把ALTER TABLE加列和CHANGE COLUMN的写法弄混或者在LEFT JOIN里把过滤条件放错位置导致结果集对不上。这篇内容我按“数据生命周期”的思路把日常工作里真正高频的MySQL常用指令做了系统整理从建库建表到数据增删改从查询、关联、索引到权限管理、事务控制、备份恢复全部配上指令细节和避坑说明。不追求把手册搬全而是让你读完就能直接照着敲、照着排查问题。适合刚接触数据库的新手建立整体认知也适合有一定经验的人当速查手册放在手边。1. 先把指令分类MySQL常用指令的正确打开方式1.1 背指令的错误姿势很多人学MySQL指令是从一条一条背开始的CREATE、DROP、INSERT各背一遍背完就忘。原因很简单脱离场景去记语法大脑留不下印象。我自己的经验是先把指令按职能归类知道一类问题该去找哪条语句再记语法细节就容易多了。MySQL的指令按职能可以分成几个大类DDL处理库表结构定义DML处理数据增删改DQL负责数据查询DCL负责权限控制TCL负责事务控制再加上备份恢复和性能分析。你先在心里搭起这个架子后面每条指令都有了归属就不会再出现“想用的时候想不起来”的情况。1.2 按数据生命周期归类指令不应该零散背应该跟着数据走。数据从出生到退役的生命周期大致是这样先建库建表DDL然后往里写数据DML接着反复查询和分析DQL中间要控制谁能访问DCL要保证并发修改不出问题TCL最后数据变多变重要了要定期备份和恢复。这张脑图一旦清晰你需要的所有指令就都串起来了。我建议初学者把这张分类表存下来就当目录用。1.3 SQL关键字的执行顺序这里还要额外讲一个对理解查询指令非常关键的知识点SQL语句的书写顺序和执行顺序是两个东西。书写顺序是SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT而逻辑执行顺序是FROM、ON、JOIN、WHERE、GROUP BY、HAVING、SELECT、DISTINCT、ORDER BY、LIMIT。为什么WHERE里不能用SELECT里定义的别名因为SELECT在WHERE之后才执行。为什么ORDER BY能用别名因为ORDER BY在SELECT之后执行。很多查询报错、结果不对根子就在这个执行顺序上。2. 库与表DDL指令的常用姿势2.1 库级操作先看库级操作这一层的指令不多但都挺关键-- 查看所有数据库 SHOW DATABASES; -- 切换当前数据库 USE db_name; -- 建库并指定字符集 CREATE DATABASE db_name DEFAULT CHARACTER SET utf8mb4; -- 修改库字符集 ALTER DATABASE db_name CHARACTER SET utf8mb4; -- 删除数据库极其危险慎用 DROP DATABASE db_name;我特别强调一点生产环境建库一定要显式指定字符集。有些旧版本默认字符集不是utf8mb4如果库表建完以后才想起改又发现数据已经有中文乱码那就是返工级别的麻烦。utf8mb4能完整支持中文和emoji等四字节字符存储空间会比utf8多一些但对绝大多数业务来说完全值得。库级操作里DROP DATABASE是最不能手滑的一条指令下去整个库没了我见过不止一次所以这条指令我几乎只在测试环境用。2.2 表级操作CREATE TABLE建表是DDL里最重要的指令没有之一。下面是实际业务里比较规范的一张用户表示例CREATE TABLE user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, name VARCHAR(50) NOT NULL DEFAULT COMMENT 用户姓名, age TINYINT UNSIGNED DEFAULT NULL COMMENT 年龄, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_name (name), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;逐项说几个容易踩坑的点。主键用INT UNSIGNED AUTO_INCREMENT可以覆盖更大的正数范围比普通INT多一倍的容量。VARCHAR要按实际长度设置不要随手写255甚至更大因为行大小有限制冗余的字段长度还会影响索引性能。DATETIME配合DEFAULT CURRENT_TIMESTAMP可以自动记录创建时间ON UPDATE CURRENT_TIMESTAMP能在更新时自动改时间省掉很多应用层代码。建完以后可以用SHOW CREATE TABLE user\G; 查看建表语句顺便确认字符集和引擎设置是否生效。这张表的默认引擎是InnoDB它是支持事务和行级锁的绝大多数业务场景都应该选它。2.3 ALTER TABLE业务迭代中避不开的修改业务跑起来以后加字段、改字段是家常便饭。ALTER TABLE的常用写法如下-- 加一列 ALTER TABLE user ADD COLUMN phone VARCHAR(20) DEFAULT COMMENT 手机号 AFTER name; -- 修改列类型 ALTER TABLE user MODIFY COLUMN age SMALLINT UNSIGNED DEFAULT NULL; -- 改列名和类型 ALTER TABLE user CHANGE COLUMN name user_name VARCHAR(64) NOT NULL DEFAULT ; -- 删除列 ALTER TABLE user DROP COLUMN phone; -- 加索引 ALTER TABLE user ADD INDEX idx_age (age);这里有个重点CHANGE COLUMN是重命名兼修改MODIFY COLUMN只修改定义不重命名两者语法不一样写错就会报语法错误。另外修改大表会有锁表的影响虽然MySQL 8.0用InnoDB在线DDL改进了一部分但对超大批量数据的表做ALTER还是要选业务低峰期执行或者考虑专门的在线改表工具。我自己处理过一张几千万行的表直接ALTER加列跑了将近半小时线上写入全部卡住从那以后凡是超大表的结构变更我都提前做好方案和回退计划。3. 数据增删改DML指令的细节3.1 INSERT的几种写法数据写入指令里INSERT有好几种变形分别应对不同场景-- 单行插入 INSERT INTO user (name, age) VALUES (张三, 25); -- 多行批量插入 INSERT INTO user (name, age) VALUES (李四, 26), (王五, 27); -- 指定列名插入 INSERT INTO user SET name 赵六, age 28; -- 忽略重复 INSERT IGNORE INTO user (name, age) VALUES (张三, 30); -- 冲突时更新 INSERT INTO user (name, age) VALUES (张三, 30) AS new ON DUPLICATE KEY UPDATE age new.age;批量插入能减少日志提交和索引维护次数写入性能提升非常明显比如一次插入一百行比一百次单行插入快好几个量级。INSERT IGNORE用于“有重复就跳过”的场景ON DUPLICATE KEY UPDATE则用于“有重复就更新”的幂等写入这两个特别适合做数据同步任务。MySQL 8.0.20以后官方推荐在ON DUPLICATE KEY UPDATE中不要再用VALUES()函数取新值而是用AS别名新写法更清晰也避免VALUES()函数在后续版本被废弃的风险。3.2 UPDATE与DELETE-- 更新 UPDATE user SET age 29 WHERE id 1; -- 删除 DELETE FROM user WHERE id 1;这一类指令最大的坑就是忘了WHERE。UPDATE不带WHERE会更新全表DELETE不带WHERE会清空全表。我自己的习惯是在写UPDATE或DELETE之前先写一条等价的SELECT看一眼WHERE条件圈回来的数据对不对再把SELECT换成UPDATE或DELETE执行。这个习惯看着多花十秒钟却能挡住绝大多数手滑事故。一次凌晨变更同事在测试环境执行UPDATE忘记WHERE把几百条配置数据全部改成一个值因为他先确认过是测试库影响还能接受但大家都不愿意让这种操作发生到生产环境。这里还要区分DELETE和TRUNCATEDELETE是DML逐行删除可以配合事务回滚但不会重置自增ID删除大表会产生大量binlog和undo日志TRUNCATE是DDL直接重建表结构速度快、不能回滚、会重置自增ID。按数据量取用别混着使。如果你只是清空一张表且确认不会回滚TRUNCATE效率远高于DELETE如果只是删小部分数据用DELETE并带上精确的WHERE条件。3.3 REPLACE的细节REPLACE INTO user (name, age) VALUES (张三, 28);REPLACE的逻辑是发现唯一键冲突时先把旧记录DELETE掉再INSERT新记录。这里有两个容易忽略的影响。一是主键或唯一键冲突时自增ID会重新分配删除再插入导致ID跳变如果其他地方引用了这个ID关联数据就可能错位。二是如果表上有外键或删除触发器等依赖旧记录被删会触发额外行为副作用比ON DUPLICATE KEY UPDATE大得多。所以两边都能满足需求时我通常推荐ON DUPLICATE KEY UPDATE至少在业务逻辑可控性上更稳。4. 查询核心SELECT指令从骨架到细节4.1 SELECT骨架和执行顺序SELECT是MySQL里使用频率最高、语法最复杂的指令。先把骨架写出来SELECT c1, c2, 聚合函数(c3) FROM t1 JOIN t2 ON 连接条件 WHERE 过滤条件 GROUP BY 分组字段 HAVING 分组后过滤 ORDER BY 排序字段 LIMIT 行数;前面提到的逻辑执行顺序在这里就起作用了。WHERE是先对行过滤GROUP BY是对WHERE之后的结果分组HAVING再过滤分组结果。所以常见的“WHERE走了索引还是慢”“为什么HAVING里能用别名”这类问题都能从执行顺序里找到答案。还有一个容易忽略的点DISTINCT去重发生在SELECT之后所以它对性能的影响在列很多时尤其明显能用其他方式去重就不必迷信DISTINCT。4.2 WHERE与ORDER BYSELECT id, name, age FROM user WHERE status 1 AND age BETWEEN 18 AND 35 ORDER BY id DESC LIMIT 20;BETWEEN包含边界值IN在MySQL 8.0对大数据集的IN做了改进不等于、LIKE通配符往往会影响索引使用这些都要在实际场景里验证。ORDER BY多字段排序用逗号分隔每个字段后接ASC或DESC。需要注意的是如果ORDER BY的字段不在索引里数据量一大就容易出现Using filesort查询会明显变慢。实际优化方式通常是给排序字段建索引或者在业务允许时减少排序行数比如先过滤掉大部分数据再排序。4.3 GROUP BY / HAVING与聚合SELECT status, COUNT(*) AS cnt, AVG(age) AS avg_age FROM user GROUP BY status HAVING COUNT(*) 10;WHERE没法用聚合函数做过滤因为聚合发生在WHERE之后所以必须用HAVING。另一个容易踩坑的点是ONLY_FULL_GROUP_BY模式MySQL 5.7之后默认开启SELECT的列要么出现在GROUP BY里要么被聚合函数包裹否则直接报错。写分组查询时选非聚合列要格外小心。我在很多老项目里见过这类SQL升级数据库版本以后突然报错原因就是在分组查询里随意挑了一个非聚合列这种写法在语义上本来就是不确定的以前只是MySQL没拦。建议新写的分组查询都按ONLY_FULL_GROUP_BY规范来别和优化器对着干。4.4 LIMIT分页与常用函数SELECT id, name FROM user ORDER BY id LIMIT 20 OFFSET 40;LIMIT 20 OFFSET 40等价于LIMIT 40, 20表示跳过40行取20行。不过深分页是个老大难问题LIMIT 1000000, 20时MySQL要先查完一百万行再丢出去越翻越慢。业务上常用两种优化一种是限定翻页深度比如只允许翻前两百页另一种是改用游标方式比如WHERE id 上一页最大id ORDER BY id LIMIT 20。做数据导出的分页查询时我更推荐游标方式因为导出数据通常量大且顺序访问游标几乎不担心偏移量累积。日常开发还会高频用到这几个函数NOW()取当前时间DATE_FORMAT格式化日期IFNULL(expr, 默认值)处理NULLCONCAT拼接字符串DATEDIFF算日期差。这类函数在报表查询里出现频率非常高建议顺手记住一两个常用的其余用到再查。注意DATE_FORMAT的参数是巨坑大小写含义完全不同写之前最好确认一下。5. JOIN与子查询多表查询的常见指令5.1 内连接与外连接业务里几乎不会只有一张表。两张表以上查询最常用的指令就是JOIN-- 内连接只返回两边匹配上的行 SELECT u.name, o.order_no FROM user u INNER JOIN orders o ON u.id o.user_id; -- 左连接左表全返回右表无匹配则NULL SELECT u.name, o.order_no FROM user u LEFT JOIN orders o ON u.id o.user_id;INNER JOIN取交集LEFT JOIN以左表为尊右表没匹配到就补NULL。实际写代码时LEFT JOIN用得比内连接还多因为业务常常要求“主表数据一条不落”。RIGHT JOIN理论上对应左连接反过来但很多团队约定一律用LEFT JOIN避免混用后逻辑混乱这个实践我认为很值得借鉴。JOIN类型选错会直接影响返回行数排查数据对不上的问题时先看JOIN类型和关联字段对不对是第一步。5.2 ON和WHERE条件千万别放错经典的坑在这里。同样是查“用户和已付款订单”一条用ON过滤状态一条用WHERE过滤状态结果完全不同-- 推荐在ON里加过滤条件保左表所有用户 SELECT u.name, o.order_no FROM user u LEFT JOIN orders o ON u.id o.user_id AND o.status 1; -- 注意在WHERE里加过滤条件左连接退化成内连接 SELECT u.name, o.order_no FROM user u LEFT JOIN orders o ON u.id o.user_id WHERE o.status 1;第二段SQL的逻辑是先把用户和订单全连接起来再用WHERE把status1的行过滤结果里没有已付款订单的用户直接被淘汰LEFT JOIN的效果就没了。排查“为什么LEFT JOIN少了一堆行”这类问题第一反应就该去看过滤条件是不是写在WHERE里了。这个坑我在代码评审里提醒过很多次几乎每个项目都有人踩。5.3 子查询的写法与优化意识子查询就是把查询结果当临时表用SELECT name FROM user WHERE id IN ( SELECT DISTINCT user_id FROM orders WHERE status 1 );子查询的优势是逻辑直观但老版本MySQL对IN子查询的优化不理想容易产生内部临时表。MySQL 8.0的优化器强了不少很多场景会自动改写数据量巨大时还是建议先EXPLAIN看一眼。如果子查询结果集大、外层表也大改成等价的JOIN有时性能更好。优化没有银弹唯一的方法就是EXPLAIN之后对比。我在一个查询慢的案例里把IN子查询改成JOIN后执行时间从两秒降到几十毫秒就是因为旧版本把子查询结果物化成临时表而JOIN可以直接用驱动表去匹配索引。6. 索引与性能分析让指令跑得更快6.1 索引用例速查查询指令本身不会无缘无故慢慢的主要原因是没走索引或者扫描行数过多。索引相关指令如下-- 创建索引 CREATE INDEX idx_age ON user (age); -- 创建复合索引 CREATE INDEX idx_status_age ON user (status, age); -- 查看表上的索引 SHOW INDEX FROM user; -- 删除索引 DROP INDEX idx_age ON user;复合索引有效性的核心是最左前缀原则建了(status, age)两列复合索引查询条件里只有status、或者status加age都能命中但只有age的查询没法命中最左前缀索引基本就白建了。建索引时还要考虑区分度性别这种只有两个取值又分布不均的字段单独建索引效果很差。索引不是越多越好写多读少的表每加一个索引都会拖慢写入和存储我的原则是先用EXPLAIN定位慢SQL再按实际查询模式建索引而不是上来就把表字段全建一遍。6.2 EXPLAIN执行计划怎么看EXPLAIN是排查查询性能最重要的指令EXPLAIN SELECT id, name FROM user WHERE status 1 AND age 20 ORDER BY id LIMIT 20;执行计划里重点看四个字段。type表示访问类型从system、const、eq_ref、ref、range到index、ALL越往右性能越差看到ALL说明全表扫描。key显示实际用到的索引。rows估算扫描行数。Extra里出现Using filesort或Using temporary多半意味着排序和分组没有利用索引要警惕。我一般先看type是不是range或ref再看rows和实际返回行数对不对得上。如果EXPLAIN显示的rows比预期多很多通常就是统计信息过期或者WHERE条件写法导致了索引失效。6.3 索引失效的典型场景实际工作里常见的索引失效场景整理成表格场景示例结果隐式类型转换WHERE phone 13800000000phone是字符串字段上发生函数转换索引失效对索引列做运算WHERE YEAR(create_time) 2024索引列参与了函数运算失效LIKE前置通配符WHERE name LIKE %张%前缀未知无法走索引OR条件包含非索引列WHERE age 18 OR status 1可能退化为全表扫描复合索引不满足最左前缀已有(status, age)只用age查询无法命中索引判断一条SQL有没有走索引最有把握的方式就是EXPLAIN别靠猜。我自己优化慢查询的习惯是先EXPLAIN看type和key发现ALL或key为空就反推WHERE条件写法再看有没有函数运算或类型转换最后决定改SQL还是加索引。7. 用户与权限DCL指令7.1 创建用户与授权数据库权限本质上就是几条指令组合。最常用的创建用户和授权写法-- 创建用户host限制来源主机 CREATE USER app% IDENTIFIED BY StrongPssw0rd; -- 授权最小权限原则 GRANT SELECT, INSERT, UPDATE, DELETE ON my_db.* TO app%; -- 全部权限本地管理员使用 GRANT ALL PRIVILEGES ON *.* TO adminlocalhost WITH GRANT OPTION;host字段要仔细设计localhost只允许本机连接%表示任意主机网段格式如192.168.1.%也能限制来源。业务应用账号建议只给所需库表的增删改查权限不要随手ALL PRIVILEGES更别让应用账号拥有GRANT OPTION。这个在出现安全事件时就是能不能控制损失的分水岭。内部系统用到的账号我一般把host配成应用服务器网段而不是放开%能少一个暴露面就少一个。7.2 权限回收与账号维护-- 查看某用户的权限 SHOW GRANTS FOR app%; -- 回收权限 REVOKE DELETE ON my_db.* FROM app%; -- 修改密码 ALTER USER app% IDENTIFIED BY NewPssw0rd; -- 删除用户 DROP USER app%;这里说一个常被误解的指令执行完GRANT或REVOKE之后到底要不要手动执行FLUSH PRIVILEGES官方明确说明使用账号管理语句CREATE USER、GRANT、REVOKE、ALTER USER等时MySQL会隐式刷新权限缓存不需要额外FLUSH。只有你手动去改mysql系统表里的授权记录才需要FLUSH PRIVILEGES让缓存重新加载。所以很多教程里那句“授权后记得flush”其实是对直接改系统表场景说的正常授权流程里可以省略。8. 事务控制保证数据一致性的指令8.1 事务控制核心指令涉及转账、下单这类对一致性要求高的业务事务指令是底线-- 开启事务 START TRANSACTION; -- 执行业务数据变更 UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; -- 确认无误提交 COMMIT; -- 中途发现有问题回滚 ROLLBACK;需要注意的是AUTOCOMMIT默认开启普通DML语句每一条都会自动提交所以必须显式START TRANSACTION才能把多条语句合成一个事务。另外很多操作其实是“隐式提交”比如执行DDL语句CREATE、ALTER、DROP等、修改MySQL系统表、加锁语句等这些操作会直接结束当前事务。所以事务内不要混入DDL不然前面做一半的DML就彻底提交了。曾经有人在一个事务里先做了一些UPDATE然后顺手ALTER了一张表结果前边的更新全被隐式提交了后面ROllback根本回不去。8.2 隔离级别与并发问题事务并发时最常见的问题有脏读、不可重复读、幻读。MySQL InnoDB引擎支持四种隔离级别查看和设置指令如下-- 查看当前隔离级别 SHOW VARIABLES LIKE transaction_isolation; -- 设置会话隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;四档隔离级别从宽到严分别是读未提交、读已提交、可重复读、串行化。MySQL默认是可重复读能通过间隙锁避免大多数幻读缺点是并发锁冲突更多读已提交在互联网业务里也很常用幻读风险更低一些但不代表没有。理解这些差异之后再选择隔离级别而不是每个项目都改到串行化去追求绝对安全那样并发能力基本就废了。实际项目里库存扣减这类高并发场景经常用乐观锁配合事务指令来保证一致性而不是单纯调高隔离级别。9. 备份与恢复保命指令9.1 mysqldump逻辑备份备份这件事等出事以后才想起就晚了。MySQL最常用的备份指令是mysqldump# 在命令行执行导出整个库 mysqldump -u root -p --single-transaction --default-character-setutf8mb4 my_db /backup/my_db_20250101.sql # 导出单张表常用于临时迁移 mysqldump -u root -p --single-transaction my_db user /backup/user.sql--single-transaction是InnoDB表做一致性快照的关键参数备份期间不锁表业务照常读写。如果漏了这个参数在MyISAM表上备份会直接锁表线上被锁几分钟等于事故。另外导出文件默认可能不包含存储过程、函数和触发器需要确认的一并加上。我个人建议备份脚本里固定加上--default-character-setutf8mb4否则还原到不同字符集的库上很容易出现乱码。9.2 恢复指令与binlog补充备份文件生成以后恢复是最简单的# 命令行导入 mysql -u root -p my_db /backup/my_db_20250101.sql # 进入MySQL客户端后也可以source source /backup/my_db_20250101.sql恢复前记得先确认目标库存在、字符集与导出一致。如果是误删一张表后的恢复通常流程是找到误删前的全量备份恢复到一个临时库再用binlog把备份之后到误删时间点的增量日志追上来。binlog是MySQL默认开启的二进制日志记录了所有变更操作可以配合mysqlbinlog工具做增量恢复。这套操作比较复杂建议团队提前写好恢复演练文档别等真出了事再上手查命令。我参加过几次恢复演练第一次手动追binlog花了将近一小时第二次直接依据演练文档十分钟内完成可见准备工作的价值。10. 高频踩坑与排查实录10.1 写数据时容易踩的坑我这里把平时答疑最多的问题集中列一下。第一UPDATE或DELETE没带WHERE全表数据被改这类事故的防线就是我前面说的“先SELECT确认”。第二DELETE之后数据不见了但磁盘空间没少因为InnoDB的聚簇索引释放空间不一定回收到操作系统需要用OPTIMIZE TABLE或重建表来压缩TRUNCATE则直接释放两者行为差异明显。第三大批量更新造成主从延迟比如一次UPDATE几百万行主库执行完从库要追很久binlog业务高峰期会把从库读接口拖垮所以大更新尽量分批拆小。10.2 读数据时容易踩的坑读数据侧的坑第一是SELECT *带回大量无用列既增加网络传输又让索引覆盖失效。第二是深分页查询LIMIT后面offset越大越慢能用游标就用游标。第三是WHERE字段和表结构字段类型不一致隐式类型转换把索引废掉例如字符串字段查数字。第四是看似走了索引实际只走了部分索引比如范围条件之后的字段无法继续使用复合索引。遇到这些情况老办法还是EXPLAIN先定位是不是type为ALL再看Extra里的Using filesort和Using temporary顺着执行计划逐项优化。10.3 管理维护时容易踩的坑最后说几个运维管理侧的坑。连接数被打满时新连接会报Too many connections这时候普通账号根本登不进去得用超级管理员权限才能救急所以应用层一定要设置连接池并合理配置最大连接数。binlog长时间不清理会把磁盘撑满MySQL默认可能不自动清理要根据业务确定binlog保留周期并配置过期时间。另外DROP DATABASE之前务必三重确认先看库名、再确认影响范围、最后宁可提前用RENAME把库改成_bak后缀观察一段时间也比直接DROP稳妥得多。最后再分享一个我自己的习惯。手动敲了这么多年MySQL指令我最大的体会是真正常用的指令只有几十条但把每条指令的边界条件搞清楚比数量重要得多。比如DELETE和TRUNCATE都能删数据但一个是DML一个是DDLLEFT JOIN里过滤条件写在ON和WHERE结果完全不同GRANT之后平时不需要手动FLUSH。我现在自己维护了一个SQL笔记文件按建表、查询、变更、备份这些场景分好段落每次解决完一个具体问题就把对应SQL和注释记进去。下次遇到同类需求直接打开复用既省事又少踩坑。建议大家也按自己的业务场景这么做时间长了这些指令自然就长在脑子里了。