
干这行这么多年MySQL基本操作命令几乎天天要敲。不管是在开发环境建个库表、帮同事排查一个连接不上的尴尬问题还是线上环境查一条慢SQL最后都得落到那几条命令上。经常有新人问我MySQL到底该怎么入门我的回答一直很简单别急着上可视化工具和框架先把命令行下那套基本操作玩明白后面所有优化、排错、架构相关的技能才有地基。这篇文章我就把自己日常用得最频繁的MySQL命令整理出来从连接登录、建表改表、增删改查到查询优化、存储过程和问题排查按实战场景走一遍希望能帮你少踩几个坑。1. 连接MySQL命令行登录与最基础的库表查看命令1.1 登录命令与初始密码处理连接MySQL最常用的就是mysql这个客户端命令基本格式是这样的mysql -h127.0.0.1 -P3306 -uroot -p参数含义很直白-h指定主机地址-P指定端口-u指定用户-p表示需要输入密码。如果是在MySQL服务器本机登录可以直接省略-h参数走默认的socket连接速度更快mysql -uroot -p这里有个很多新手第一次就卡住的问题就是MySQL安装完以后初始密码到底在哪MySQL 8.0在初始化安装时会生成一个临时密码这个密码通常会写到错误日志里。Linux下可以这样找grep temporary password /var/log/mysqld.logWindows下则在MySQL数据目录下的*.err文件里搜。拿到临时密码登录后MySQL会要求你先改密码否则什么都干不了。改密码的命令如下ALTER USER rootlocalhost IDENTIFIED BY 新的强密码;这里强调一下如果你装的是MySQL 8.0默认密码插件是caching_sha2_password密码复杂度要求也比较高至少得包含大写、小写、数字和特殊字符。曾经我见过不少同事一上来就想设个123456结果直接被策略拒了还以为是自己操作有问题。1.2 库表查看与字符集检查登录MySQL之后先养成一个好习惯确认自己在哪个库表结构是什么样。最基础的一组查看命令如下SHOW DATABASES; USE your_db; SHOW TABLES; DESC your_table; SHOW CREATE TABLE your_table\GSHOW DATABASES列出所有数据库USE切换当前库SHOW TABLES查看库下所有表DESC查看表字段信息SHOW CREATE TABLE则能看到建表语句的完整定义。我强烈建议你在排查表结构问题时用最后一条因为DESC只显示字段名、类型、默认值这些基础信息而SHOW CREATE TABLE能看到索引、约束、字符集、自增值等全量细节还可以直接复制出来改一版新表。字符集问题也是新手重灾区。登录后先看几个变量SHOW VARIABLES LIKE character_set%;如果发现character_set_server不是utf8mb4建议在配置文件里固定成utf8mb4。现在的新表基本无脑用utf8mb4它能覆盖emoji和生僻字兼容性最好。曾经有次线上插入一条带emoji昵称的数据直接报错查了半天才发现库表字符集还是老旧的utf8mb3改成utf8mb4后问题消失这种细节在文本、评论类业务里特别常见。2. DDL操作建表、改表、删表的基本命令与选型细节2.1 建表语句字段类型、主键与自增DDL是Data Definition Language建表、改表、删表都归它管。一张表的建表语句写得好不好直接决定了后面查询快不快、扩展顺不顺。我常用的建表模板大概长这样CREATE TABLE user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(64) NOT NULL DEFAULT COMMENT 用户名, age INT NOT NULL DEFAULT 0 COMMENT 年龄, balance DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 余额, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username), KEY idx_age (age) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT用户表;在字段类型选择上几个建议供参考整型主键优先BIGINT而不是INT避免单表数据量大以后自增主键溢出金额字段一定要用DECIMAL不能图省事用FLOAT或DOUBLE否则算钱算到分的时候会出误差状态类字段用TINYINT比字符串省空间也方便扩展时间字段建议用DATETIME范围足够日常使用TIMESTAMP到2038年会有溢出风险。默认值也是建表时容易忽略的点。热搜里经常出现“mysql设置默认值为0”其实就是字段定义里的DEFAULT 0。对于数字、字符串、时间这类字段尽量都设上默认值能避免不少因为插入数据时漏了字段导致的NULL问题。特别提醒一句TEXT/BLOB类型在MySQL里不能有默认值如果你真需要给大文本字段一个“默认空字符串”要么用VARCHAR要么在业务层处理。2.2 修改表结构ALTER TABLE和索引管理上线需求经常要加字段、加索引最常用的就是ALTER TABLE。加字段的语法ALTER TABLE user ADD COLUMN nickname VARCHAR(32) NOT NULL DEFAULT COMMENT 昵称 AFTER username;AFTER关键字可以控制新字段加在哪个字段后面不加的话默认加到最后一列。修改字段类型或默认值用MODIFYALTER TABLE user MODIFY COLUMN age INT NOT NULL DEFAULT 18 COMMENT 年龄;改字段名用CHANGEALTER TABLE user CHANGE COLUMN nickname nick_name VARCHAR(32) NOT NULL DEFAULT COMMENT 昵称;注意CHANGE后面要写两遍字段名先旧后新很多新手容易漏。删字段用DROP COLUMN这个操作要谨慎列数据会一并删掉且不可恢复。索引操作也走ALTER或者用独立的CREATE/DROP INDEXALTER TABLE user ADD INDEX idx_username_age (username, age); CREATE INDEX idx_age ON user (age); ALTER TABLE user DROP INDEX idx_age; DROP INDEX idx_age ON user;MySQL创建索引的命令不难难在索引怎么设计。一个经验之谈联合索引要遵循最左前缀原则查询条件里如果没有联合索引的最左字段索引基本用不上。比如建立(username, age)联合索引后WHERE age 20就享受不到这个索引的优化但WHERE username xx可以。2.3 DROP、TRUNCATE、DELETE到底有啥区别这三个命令都能让数据消失但行为完全不同我用一个表格把核心差异列出来操作能否带WHERE是否触发触发器是否重置自增能否回滚DELETE可以会不会可以TRUNCATE不可以不会会不可以DROP不可以不会表都没了不可以DELETE是逐行删除走事务可以配合WHERE精准删数据删错还能ROLLBACK救回来。TRUNCATE会把整张表清空并重置自增ID速度非常快但它是隐式提交一旦执行就没有回旋余地。DROP更彻底直接把表结构和数据全部干掉。我在实际工作中有一条死规矩生产环境上DROP和TRUNCATE必须经过确认再加锁或备份DELETE必须带WHERE不带WHERE的DELETE相当于自毁。如果要清空一张表但保留表结构多数情况下TRUNCATE比DELETE快得多但前提是你要能接受自增ID被重置。3. 数据操作INSERT、UPDATE、DELETE的进阶用法与坑3.1 INSERT插入单行、多行与INSERT SELECTINSERT是写入数据的根本方式最基础的写法INSERT INTO user (username, age, balance) VALUES (zhangsan, 25, 100.00);一次插入多行时可以在VALUES后面跟多组括号中间用逗号分隔INSERT INTO user (username, age, balance) VALUES (lisi, 26, 200.00), (wangwu, 27, 300.00);批量插入比一条条INSERT快得多这背后是减少客户端和服务端交互次数的原理。如果要从另一张表搬运数据可以用INSERT ... SELECTINSERT INTO user_backup (username, age, balance) SELECT username, age, balance FROM user WHERE age 30;这里有个容易踩的坑SELECT出来的字段类型、长度、顺序必须和目标表能对得上否则会报Data too long或者字段数量不匹配。另外如果目标表有唯一索引批量插入时遇到重复键会整条语句报错MySQL提供了一个很实用的语法INSERT ... ON DUPLICATE KEY UPDATEINSERT INTO user (username, age) VALUES (zhangsan, 30) ON DUPLICATE KEY UPDATE age VALUES(age);意思是如果唯一索引冲突就改成更新。这个语法在同步数据、幂等写入场景里非常好用能少写一大段判断逻辑。3.2 UPDATE更新语法与子查询冲突UPDATE的基本语法UPDATE user SET age 26 WHERE username zhangsan;支持同时更新多个字段用逗号分隔。也支持多表关联更新UPDATE user u JOIN user_order o ON u.id o.user_id SET u.balance u.balance - o.amount WHERE o.status 1;这里JOIN的用法很实用等于把另一张表的计算条件拿到UPDATE语句里一起执行。但MySQL里有个著名限制UPDATE时不能在同一语句中直接查询并修改同一张表。比如下面这个写法会直接报错You cant specify target table user for update in FROM clauseUPDATE user SET status 1 WHERE id IN (SELECT id FROM user WHERE age 18);解决方式也很经典把子查询再包一层派生表UPDATE user SET status 1 WHERE id IN ( SELECT id FROM ( SELECT id FROM user WHERE age 18 ) AS tmp );热搜词“mysql中更新子查询”指的就是这个场景。原理是MySQL不允许对目标表进行子查询时直接做修改套一层临时表就能绕开限制。还有一个细节UPDATE语句里如果WHERE走了主键或唯一索引影响行数通常是1否则可能影响多行修改前最好先确认条件范围。严格养成习惯的话线上更新大批量数据最好分批次LIMIT更新减少锁范围和主从延迟。3.3 DELETE删除的安全操作习惯DELETE语法本身不复杂DELETE FROM user WHERE id 100;但删除操作是生产事故高发区。最常见的问题就是忘了带WHERE或者WHERE写得不对把不该删的数据删了。我的做法是写DELETE之前先把同样的WHERE条件放到SELECT里查一遍确认影响的数据量符合预期再换成DELETE执行。比如SELECT id FROM user WHERE age 10; DELETE FROM user WHERE age 10;如果数据量特别大比如一次要删几百万行直接DELETE会把大量行锁住还可能拖垮主从复制。更稳妥的办法是分批删除DELETE FROM user WHERE age 10 LIMIT 1000;循环执行这个语句直到影响行数为0。这种操作方式虽然慢但对线上业务最友好。要清空整张表又不需要回滚时TRUNCATE比DELETE快很多它能直接释放表空间而不是一点点删行。4. SELECT查询排序、连接、分组与执行计划4.1 ORDER BY排序与LIMIT分页查询是MySQL使用频率最高的操作先看排序。ORDER BY支持一个或多个字段还能指定升降序SELECT username, age FROM user ORDER BY age DESC, id ASC LIMIT 20;这个语句的含义是优先按age降序排age相同再按id升序排。注意如果你对多个字段排序每个字段后面的DESC/ASC都要写清楚不写默认ASC。分页通常配合LIMIT使用SELECT * FROM user ORDER BY id LIMIT 0, 20; SELECT * FROM user ORDER BY id LIMIT 20 OFFSET 0;两条写法等价LIMIT后面第一个参数是偏移量第二个是返回条数。但这里要提醒一个深分页性能问题当偏移量非常大时比如LIMIT 200000, 20MySQL仍然需要扫描并丢弃前200000行效率很低。我处理这种场景通常会改成基于主键的游标方式SELECT * FROM user WHERE id 200000 ORDER BY id LIMIT 20;前提是业务允许用上一页的最大id作为下一页的起点这种方式在百万级数据下也能保持稳定速度。另外日常开发中我很少用SELECT *更习惯把需要返回的字段名显式写出来一是减少不必要的数据传输二是避免后面表结构加了字段导致返回结果集变化影响接口兼容性。4.2 JOIN连接INNER、LEFT、RIGHT到底怎么选JOIN是MySQL里最容易搞混的概念。简单说JOIN就是把两张表按某个条件横向拼接起来。INNER JOIN只返回两表都匹配得上的行LEFT JOIN返回左表全部行右表没有匹配就补NULLRIGHT JOIN反过来返回右表全部行左表无匹配补NULL。SELECT u.username, o.order_no FROM user u INNER JOIN user_order o ON u.id o.user_id; SELECT u.username, o.order_no FROM user u LEFT JOIN user_order o ON u.id o.user_id;第一句查出来的是所有下过单的用户和他们的订单号没下过单的用户不会出现第二句查出来的是所有用户以及订单号没下过单的用户order_no会是NULL。我个人的经验是RIGHT JOIN用得很少因为只要把表的顺序调换一下RIGHT JOIN的效果就能用LEFT JOIN实现统一用LEFT JOIN反而更不容易出错。JOIN时还有一个不容忽视的点ON和WHERE的过滤时机不同。LEFT JOIN中写在ON里的条件只影响右表匹配而写在WHERE里的条件会在左表保留逻辑之前参与过滤结果可能完全不同。如果你发现LEFT JOIN结果中左表行数少了多半是本来想放在ON里的条件被放进了WHERE。4.3 GROUP BY与HAVING的实践细节GROUP BY的作用是把相同字段值的行合并成一组通常配合聚合函数使用比如COUNT、SUM、AVG、MAX、MINSELECT status, COUNT(*) AS cnt FROM user GROUP BY status;这句话就是按状态分组统计每个状态下的用户总数。如果还要对聚合后的结果做过滤要用HAVING而不是WHERESELECT status, COUNT(*) AS cnt FROM user GROUP BY status HAVING cnt 10;WHERE是在分组之前对原始行进行过滤HAVING是在分组之后对聚合结果进行过滤这个区别必须记牢。另一个高频坑是ONLY_FULL_GROUP_BY模式。MySQL 5.7之后默认开启了sql_mode中的ONLY_FULL_GROUP_BY意味着SELECT后面只能出现GROUP BY里的字段以及聚合函数。如果查了不在GROUP BY里的字段MySQL会直接报错。比如下面这句在开启该模式后会报错SELECT username, age FROM user GROUP BY status;因为username和age既不在GROUP BY里也不是聚合结果。这种写法的本意可能是想取每组里的某一行但在MySQL里并不安全需要改成子查询或窗口函数实现。另外GROUP BY字段如果走了索引速度会快很多这提醒我们在设计高频分组查询时尽量让分组字段成为索引的一部分。4.4 用EXPLAIN看执行计划凡是我写的SELECT语句如果在测试环境或者线上有性能问题第一件事就是看执行计划EXPLAIN SELECT u.username, o.order_no FROM user u LEFT JOIN user_order o ON u.id o.user_id WHERE u.age 20;执行计划返回一张结果表关键字段就这么几个type表示访问类型从好到差大致是system、const、eq_ref、ref、range、index、ALL。ALL就是全表扫描通常是性能瓶颈。key表示实际用到的索引rows是MySQL估算的需要扫描的行数Extra里如果出现Using filesort或Using temporary说明排序或分组没有用到索引需要重点优化。看懂执行计划后很多慢查询的原因就清楚了要么没建索引要么索引建了但没被选中要么因为函数、隐式类型转换导致索引失效。比如对索引字段使用WHERE DATE(create_time) 2024-01-01索引大概率失效应该写成范围查询create_time 2024-01-01 AND create_time 2024-01-02。这类细节光背命令没用只能实际跑EXPLAIN多对比。5. 事务、存储过程与运维高频命令5.1 事务控制命令与隔离级别MySQL的InnoDB引擎支持事务事务能让一组操作要么全部成功、要么全部回滚。最常用的事务控制命令就三个START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT;当两条UPDATE都执行成功后COMMIT才会提交如果中间某一步失败执行ROLLBACKROLLBACK;就可以把这两条UPDATE的影响全部撤销。这个“要么全做要么全不做”的特性就是原子性。事务还有一致性、隔离性、持久性合起来叫ACID。隔离性靠的是隔离级别控制MySQL默认是REPEATABLE READ可重复读它保证同一个事务里多次读同一行数据结果一致。除了默认级别还有READ UNCOMMITTED、READ COMMITTED、SERIALIZABLE。关于MVCC多版本并发控制它是InnoDB隔离级别的核心机制。简单说每一行数据在更新时都会保留旧版本的快照事务读取时通过版本链和Read View来判定能看到哪个版本从而实现读写不互相阻塞。这也是为什么在REPEATABLE READ下一个事务里多次SELECT能看到一致快照的原因。这部分理解起来有点抽象但不用着急先在行为层面记住“读写不阻塞”这个特性就行。5.2 存储过程的声明、变量与调用存储过程就是一段预先编译好的SQL逻辑适合封装复杂、重复的业务操作。声明一个最简单的存储过程DELIMITER // CREATE PROCEDURE get_user_count(IN p_age INT, OUT p_count INT) BEGIN SELECT COUNT(*) INTO p_count FROM user WHERE age p_age; END // DELIMITER ;这里DELIMITER //的作用是把语句分隔符临时改成//因为存储过程内部有多条SQL默认的;会提前结束语句。写完再改回;。调用方式CALL get_user_count(18, cnt); SELECT cnt;IN参数是传入值OUT参数是输出值INOUT参数既能传进也能传出。查询结果通过SELECT ... INTO赋给输出变量。删除存储过程用DROP PROCEDURE IF EXISTS get_user_count;。存储过程在金融对账、定时统计这类场景还有一定价值但说实话现在大部分团队把复杂逻辑放在应用层处理存储过程用得越来越少了。我建议新手把它当作理解MySQL编程能力的窗口但不要事事都往存储过程上套因为调试和维护成本确实比应用代码高不少。5.3 运维必备processlist、status、variables日常运维排查问题时有几个命令比任何可视化工具都直接。当数据库卡顿、有查询长时间不返回时第一时间看当前连接都在干什么SHOW FULL PROCESSLIST;这个命令会列出所有连接ID、用户、库、命令类型、执行时间和SQL内容。如果发现某个连接长时间处于Sleep状态又或者某条UPDATE执行了几千秒基本可以定位到问题来源。锁死时还可以强制杀掉某个连接KILL 12345;看MySQL当前的运行状态指标用SHOW STATUSSHOW GLOBAL STATUS LIKE Threads_connected; SHOW GLOBAL STATUS LIKE QPS;前者看当前连接数后者看每秒查询数。连接数突然飙升通常要检查是不是有慢查询堆积或者连接池配置不合理。需要看配置参数时用SHOW VARIABLESSHOW VARIABLES LIKE %max_connections%; SHOW VARIABLES LIKE %timeout%;查看表的索引信息用SHOW INDEX FROM user;排查空间占用情况information_schema系统库里可以直接查SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema your_db ORDER BY table_rows DESC;这些命令单看都不复杂但组合起来就是一套完整的数据库体检流程先看进程、再看状态、然后定位慢SQL和执行计划。6. 常见问题排查安装、连接与版本兼容6.1 安装后的初始密码和端口问题MySQL安装配置教程里最常被问到的就是初始密码和端口。前面提过MySQL 8.0的临时密码会写到错误日志里Linux下路径一般是/var/log/mysqld.log。如果翻遍日志都没找到临时密码还有一个备用方案在配置文件里临时加一行skip-grant-tables重启MySQL后可以免密登录然后手动重置密码。操作过程大致如下# 编辑 /etc/my.cnf在 [mysqld] 下加 skip-grant-tables # 重启服务 systemctl restart mysqld # 免密登录 mysql -uroot # 重置密码 FLUSH PRIVILEGES; ALTER USER rootlocalhost IDENTIFIED BY 新密码;改完以后一定要把配置文件里的skip-grant-tables注释或删掉再重启服务。这个方法能救命但也非常危险因为只要它是开启状态任何本机进程都可以免密登录数据库所以不能在生产环境长期开着。端口问题也很典型。默认端口3306如果启动时报错或者外网连不上先检查端口是否被占用netstat -tlnp | grep 3306如果被别的进程占用了要么改MySQL的port配置要么处理占用进程。云服务器上还要注意安全组是否放通了3306端口这是我遇到过好多次的“MySQL配置完全没问题但远程就是连不上”的原因。6.2 Navicat、Workbench连接不上怎么办用Navicat或MySQL Workbench连接MySQL报错原因五花八门但排查思路比较固定。Navicat连接不上时按顺序检查这几个点MySQL服务是否启动systemctl status mysqld或service mysql status。端口是否可达在客户端机器上执行telnet 服务器IP 3306不通就要检查防火墙和安全组。用户主机权限MySQL的用户由“用户名主机”共同构成比如rootlocalhost就只能在本地登录。如果要允许远程需要设置root%或者专门建一个远程用户CREATE USER app% IDENTIFIED BY 密码; GRANT ALL PRIVILEGES ON your_db.* TO app%; FLUSH PRIVILEGES;密码插件兼容性MySQL 8.0默认用caching_sha2_password比较老版本的Navicat或驱动可能不支持表现是连接时直接报错。解决办法是把用户的插件改成mysql_native_passwordALTER USER app% IDENTIFIED WITH mysql_native_password BY 密码;MySQL Workbench的使用教程里核心就是填入主机、端口、用户名、密码测试连接成功后就能进入图形界面执行SQL、管理表结构和数据导出。它和Navicat的区别主要是跨平台和定位Workbench官方免费Navicat功能更丰富按个人习惯选就行。6.3 驱动版本与框架报错怎么定位版本兼容问题在Java、Python项目里特别常见。比如Django项目启动时如果报django.db.utils.NotSupportedError: MySQL 8.4 or later is required (found 8.0)这个报错通常不是要求你必须装MySQL 8.4而是Django版本检查逻辑认为当前MySQL版本不满足要求。解决思路有两种其一如果MySQL是8.0的长期支持版本就把Django版本降级到与MySQL 8.0兼容的版本其二如果是新版Django确实要求更新的MySQL版本那就得升级数据库或换个兼容的驱动。这里核心思路是先看报错来自哪个依赖库再根据依赖库的官方支持矩阵调整版本别盲目升级或降级。另一个常见场景是Sqoop连接不上MySQL。Sqoop是数据迁移工具连不上时重点检查JDBC驱动JAR包是否放在正确目录、连接串格式是否正确、MySQL用户是否允许从Sqoop所在主机访问。连接串里还要显式加参数控制编码和SSL比如jdbc:mysql://192.168.1.100:3306/your_db?useSSLfalsecharacterEncodingutf8如果是用Docker安装MySQL还要注意容器端口映射和数据持久化。简单示例docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpass \ -e MYSQL_ROOT_HOST% \ -v mysql_data:/var/lib/mysql \ mysql:8.0MYSQL_ROOT_HOST%很关键没有它root默认只允许容器内访问宿主机和外部工具都连不上。数据目录挂载到宿主机卷上则能避免容器删除后数据全丢。最后分享一个我个人的工作习惯不管操作哪个环境的数据库只要涉及UPDATE或DELETE我都会先把WHERE条件放进SELECT里跑一遍确认影响行数和数据内容符合预期再回头执行修改语句。这个过程多花不了几秒钟但能在关键时刻避免删错数据、改错数据的事故。另外把常用的MySQL命令整理成属于自己的速查表遇到问题直接查比硬记可靠得多。MySQL的基本操作命令就这么多但每一条背后都藏着不少细节真正用过、踩过坑才算真正掌握。