1. 为什么非得从命令行开始学MySQL——一个被低估的底层认知入口
很多人一接触数据库,就急着打开图形化工具,点点鼠标建库建表,看起来效率很高。但我在带过几十个初学者项目后发现:凡是跳过命令行、直接上GUI的,三个月后几乎都会卡在同一个地方——遇到报错不知道从哪查起,写一条复杂查询总要反复试错,连最基础的字符集乱码都搞不定。这不是能力问题,而是缺了一层“数据库到底在想什么”的直觉。
命令行客户端mysql.exe(或mysql命令)不是过时的古董,它是MySQL服务器和人之间最短、最透明的对话通道。它不加修饰地把服务器返回的每一行结果、每一个错误码、每一条警告原样抛给你。你输入SELECT * FROM users;,它就老老实实返回数据;你输错成SELECt * FROM users;,它不会帮你自动纠正,而是冷冰冰甩出ERROR 1064 (42000): You have an error in your SQL syntax...——这个错误本身,就是你理解SQL解析器工作方式的第一课。
我见过太多人,在Navicat里点几下就建好一张表,却说不清ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci这一长串参数里,哪部分管存储结构,哪部分管中文显示,哪部分影响排序规则。而当你在命令行里亲手敲下CREATE TABLE test (id INT) ENGINE=MyISAM;再立刻执行SHOW CREATE TABLE test;,你会亲眼看到MySQL自动给你补全了默认的字符集、校对规则、甚至自动加上了AUTO_INCREMENT的提示——这种“所见即所得+所敲即所思”的反馈闭环,是任何图形界面都无法替代的认知训练。
更重要的是,生产环境里,你几乎不可能有图形界面。运维同学发来一台云服务器的SSH地址,你第一件事就是mysql -h 10.0.1.5 -u app_user -p连上去看日志表;DBA半夜告警说主库慢查询飙升,你得立刻登录,用SHOW PROCESSLIST;抓当前活跃连接;甚至本地开发时,Docker容器里的MySQL服务,默认就只暴露命令行端口。命令行不是“备选方案”,它是数据库工程师的呼吸面罩——你可以不用它建漂亮UI,但绝不能没有它诊断问题。
所以这篇内容不叫“MySQL入门”,它叫“MySQL命令行生存指南”。我们不追求花哨功能,只聚焦一件事:让你在纯黑底白字的终端里,稳、准、快地完成所有基础操作。从第一次输入密码回车的忐忑,到能闭着眼敲出mysqldump --single-transaction --routines --triggers db_name > backup.sql这样的备份命令,中间差的不是时间,而是对每个符号背后逻辑的确认。现在,我们从最原始的登录开始。
2. 登录与登出:不只是账号密码,更是连接上下文的建立与释放
命令行登录MySQL,表面看只是一条命令,背后却牵扯到网络协议、用户权限模型、连接生命周期三个层面。很多人卡在第一步,不是密码错了,而是没搞清“连接上下文”这个概念。
2.1 登录命令的完整语法拆解
最简形式是mysql -u root -p,但这只是冰山一角。实际生产中,你几乎总会用到更完整的参数组合:
mysql -h 127.0.0.1 -P 3306 -u app_user -p'your_password' -D myapp_db --default-character-set=utf8mb4我们逐个解释这些参数的真实含义,而不是照本宣科:
-h 127.0.0.1:指定主机地址。这里必须写127.0.0.1,而不是localhost。这是关键区别!localhost会触发Unix socket本地连接(绕过TCP/IP协议栈),而127.0.0.1强制走TCP/IP。很多初学者在Docker或远程连接时死活连不上,就是因为用了localhost却没开socket文件权限。实测下来,只要网络通,用127.0.0.1永远比localhost更可靠。-P 3306:大写的P,指定端口号。注意不是小写p(那是密码参数)。MySQL默认端口是3306,但如果你改过配置文件my.cnf里的port=值,这里就必须同步修改。我见过有人在云服务器上把端口改成3307,结果本地用默认3306死命连,折腾两小时才发现是端口没对上。-u app_user:指定用户名。这个用户必须在MySQL的mysql.user系统表里存在,且host字段匹配你的连接来源。比如用户记录是'app_user'@'10.0.1.%',那你从10.0.1.5连可以,但从127.0.0.1连就会被拒绝。权限不是全局的,是“用户+来源IP”的组合体。-p'your_password':小写p加密码。注意这里不能有空格!-p '123'是错的,会被解释成“密码为空,然后多了一个参数'123'”。正确写法是-p'123'或-p123(后者仅限纯数字密码)。更安全的做法是省略密码,用-p回车后手动输入,避免密码明文留在shell历史记录里。-D myapp_db:-D是--database的缩写,表示登录后自动选择该数据库。这相当于登录后立刻执行USE myapp_db;。好处是省一步,坏处是如果库不存在,登录会直接失败并退出,你连mysql提示符都看不到。新手建议先不加,登录成功后再手动USE。--default-character-set=utf8mb4:强制客户端使用utf8mb4字符集。这是解决中文乱码的终极开关。即使服务器配置了utf8mb4,客户端若用latin1连接,插入中文照样变问号。这个参数必须和服务器character_set_server、数据库DEFAULT CHARACTER SET三者一致,缺一不可。
提示:把常用连接命令保存为shell别名,比记参数强十倍。在
~/.bashrc里加一行:alias mysql-prod='mysql -h 10.0.2.10 -P 3306 -u prod_admin -p -D production_db --default-character-set=utf8mb4'
下次直接敲mysql-prod,输密码就进去了。我团队里每个人都有一套自己的别名,按环境(dev/staging/prod)和角色(admin/readonly/analytics)分类。
2.2 登录失败的四大高频原因与排查链路
当mysql -u ... -p回车后卡住或报错,别急着重装MySQL,按这个顺序查:
| 现象 | 最可能原因 | 验证命令 | 解决方案 |
|---|---|---|---|
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111) | MySQL服务根本没启动 | systemctl status mysqld或ps aux | grep mysql | sudo systemctl start mysqld |
ERROR 1045 (28000): Access denied for user 'root'@'localhost' | 用户不存在或密码错 | sudo mysql -u root(跳过密码)→SELECT User,Host FROM mysql.user; | 重置密码或创建新用户 |
ERROR 1130 (HY000): Host '192.168.1.100' is not allowed to connect to this MySQL server | 用户host限制太严 | SELECT User,Host FROM mysql.user WHERE User='your_user'; | CREATE USER 'user'@'%' IDENTIFIED BY 'pwd'; GRANT ALL ON *.* TO 'user'@'%'; |
| 连上了但中文显示??? | 客户端字符集不匹配 | mysql> SHOW VARIABLES LIKE 'character_set%'; | 加--default-character-set=utf8mb4参数 |
特别强调第三点:'user'@'%'中的%是通配符,代表任意主机,但不包括localhost!MySQL把localhost视为特殊环回地址,和127.0.0.1分属不同host。所以如果你需要本地和远程都能连,得分别创建'user'@'localhost'和'user'@'%'两个用户,或者直接用'user'@'127.0.0.1'(它能覆盖本地TCP连接)。
2.3 登出不止是exit:连接资源的显式释放
登出命令很简单:EXIT;、QUIT;或\q(反斜杠开头是mysql客户端特有命令,不是SQL语句)。但很多人不知道,不登出直接关终端,连接并不会立刻消失。
MySQL服务器会给每个连接分配内存和线程资源。如果你用Ctrl+C强行中断mysql客户端,连接状态会变成Sleep,持续占用资源,直到超时(默认wait_timeout=28800秒,即8小时)。大量僵尸连接会拖慢服务器,甚至触发max_connections上限导致新连接被拒。
所以我的硬性习惯是:每次操作完,必敲\q。在脚本里更要显式关闭:
# 错误:没登出,连接残留 mysql -u user -p -e "SELECT COUNT(*) FROM logs;" # 正确:用-e执行完自动退出,无残留 mysql -u user -p -e "SELECT COUNT(*) FROM logs;" > result.txt还有一种隐藏风险:在同一个终端多次登录。比如你mysql -u root -p进去了,又在mysql提示符里敲mysql -u app -p——这会启动第二个mysql客户端进程,嵌套在第一个里面。此时\q只能退出内层,外层还在。用ps aux \| grep mysql就能看到两个进程。解决办法只有一个:Ctrl+D或exit彻底退出当前shell会话,重新开始。
3. 数据库与表结构操作:从CREATE到SHOW,理解元数据的本质
在命令行里建库建表,不是为了炫技,而是为了看清“数据库”和“表”到底是什么东西。图形界面点一下就出来的列表,背后是实实在在的文件和内存结构。
3.1 创建数据库:字符集与排序规则的实战选择
创建数据库的命令是CREATE DATABASE db_name;,但没人会这么裸写。必须带上字符集和排序规则:
CREATE DATABASE myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;这里有两个关键决策点:
为什么是utf8mb4,不是utf8?
MySQL的utf8是阉割版,最多存3字节字符(如中文),但不支持emoji(4字节)。utf8mb4才是真正的UTF-8实现。从MySQL 8.0开始,utf8mb4已是默认,但老版本或迁移项目必须显式声明。我经手的三个线上事故,全是因utf8存emoji导致数据截断,前端显示空白。
排序规则(COLLATE)怎么选?utf8mb4_0900_ai_ci是MySQL 8.0的推荐规则:
_0900:基于Unicode 9.0标准,排序更准确_ai:Accent Insensitive(忽略音调,如é和e视为相同)_ci:Case Insensitive(忽略大小写)
对比旧规则utf8mb4_general_ci,它在德语、法语等语言排序时有偏差。比如SELECT * FROM words WHERE word = 'café',用general_ci可能查不到带重音符的记录。除非有遗留系统兼容要求,否则一律用0900_ai_ci。
注意:数据库字符集是默认值,不是强制锁死。建表时仍可单独指定,但强烈建议保持一致。否则
db1用utf8mb4,db2用latin1,跨库JOIN时字符集转换会引发隐式转换警告,性能暴跌。
3.2 建表语句的骨架与血肉:字段定义背后的存储逻辑
CREATE TABLE是命令行里最常敲的语句,但多数人只关注字段名和类型,忽略了约束和存储引擎。一个健壮的表结构应该像这样:
CREATE TABLE users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) NOT NULL UNIQUE COMMENT '登录名,32字符内', email VARCHAR(255) NOT NULL UNIQUE, status TINYINT NOT NULL DEFAULT 1 COMMENT '0-禁用,1-启用', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_username (username), INDEX idx_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户主表';逐行解析设计意图:
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY:
主键必须是BIGINT而非INT。INT最大值21亿,互联网应用用户量轻松破亿,ID用完会导致雪崩。UNSIGNED让范围翻倍(0~42亿)。AUTO_INCREMENT由MySQL自增,但要注意:自增值是事务不安全的,高并发下可能跳号(如事务回滚后ID不回收),这是正常现象,别当成bug。username VARCHAR(32):VARCHAR是变长字符串,比CHAR省空间。长度32是经验值:微信昵称上限32,GitHub用户名上限39,够用且留余量。超过长度会报错Data too long for column,不是截断!这点和某些ORM默认行为不同,必须明确。status TINYINT NOT NULL DEFAULT 1:
用整数存状态,比ENUM或VARCHAR('active','inactive')高效。TINYINT只占1字节,ENUM内部也转成整数,但变更枚举值要ALTER TABLE锁表。DEFAULT 1确保插入时不填status字段也能成功。created_at和updated_at:DEFAULT CURRENT_TIMESTAMP是MySQL 5.6+特性,自动设创建时间。ON UPDATE CURRENT_TIMESTAMP让更新时自动刷新updated_at。注意:一个表只能有一个CURRENT_TIMESTAMP列设为DEFAULT,另一个必须用ON UPDATE。INDEX idx_username (username):
显式建索引!UNIQUE约束会自动建唯一索引,但普通查询字段(如WHERE status=1)必须手动建索引,否则全表扫描。索引名idx_前缀是行业惯例,一眼看出是索引。ENGINE=InnoDB:
必须显式指定!MySQL 5.5+默认引擎是InnoDB,但老版本或配置错误可能还是MyISAM。InnoDB支持事务、行锁、外键,MyISAM只支持表锁,高并发下会成为瓶颈。我见过一个订单表用MyISAM,秒杀活动时整个表被锁死,下单请求排队。
3.3 SHOW命令家族:窥探MySQL元数据的X光机
建完库表,别急着插数据,先用SHOW命令摸清它的底细。这是DBA每天必做的功课:
SHOW DATABASES;:列出所有数据库。注意information_schema、performance_schema、sys是系统库,别删。SHOW TABLES;:当前库下的所有表。加FROM db_name可查其他库:SHOW TABLES FROM myapp;SHOW CREATE TABLE users\G:\G是mysql客户端特有语法,让结果竖排显示,比横排易读百倍。输出里包含建表的完整SQL,包括引擎、字符集、索引,是还原表结构的唯一权威来源。DESCRIBE users;或DESC users;:快速查看字段名、类型、是否允许NULL、键类型(PRI主键,UNI唯一,MUL普通索引)。比SHOW COLUMNS FROM users;更简洁。SHOW INDEX FROM users;:查看所有索引详情。重点关注Key_name(索引名)、Seq_in_index(字段在联合索引中的顺序)、Cardinality(索引基数,值越大选择性越好)。如果Cardinality接近0,说明索引失效,要检查查询条件。SHOW FULL PROCESSLIST;:查看所有连接和正在执行的SQL。State列显示Sending data(正在处理)、Locked(被锁)、Copying to tmp table(用临时表)等状态,是定位慢查询的第一现场。
实操心得:把
SHOW命令当“体检报告”用。每次上线新表,必执行SHOW CREATE TABLE 表名\G和SHOW INDEX FROM 表名;,截图存档。半年后有人问“这个字段为什么没索引”,你翻记录就能答:“当时评估查询频次低,且基数小,建索引收益不如维护成本”。
4. CRUD操作:SQL语句不是魔法咒语,而是精确的指令集
增删改查是数据库的肌肉记忆,但命令行里执行它们,会让你对SQL的执行计划、事务边界、错误处理有刻骨铭心的理解。
4.1 INSERT:单行、多行与安全模式的博弈
最基础的插入:
INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com');但生产环境远不止于此:
多行插入:一次插入多条,比循环执行N次快10倍以上(减少网络往返和解析开销):
INSERT INTO users (username, email) VALUES ('bob', 'bob@example.com'), ('charlie', 'charlie@example.com'), ('diana', 'diana@example.com');INSERT IGNORE vs ON DUPLICATE KEY UPDATE:
当主键或唯一键冲突时,处理策略决定数据一致性:
INSERT IGNORE INTO users ...:冲突时静默跳过,不报错。适合“去重导入”场景,但无法知道哪条被跳过了。INSERT INTO users ... ON DUPLICATE KEY UPDATE status=1;:冲突时执行UPDATE。比如用户注册时,邮箱已存在则激活账号。这是幂等操作的核心。
严格模式(STRICT_TRANS_TABLES)的影响:
MySQL默认开启严格模式。这意味着:
- 插入超长字符串(如
VARCHAR(10)插15个字符)会报错,不是截断。 - 插入
NULL到NOT NULL字段会报错。 - 数字溢出(如
TINYINT插300)会报错。
关掉严格模式很危险,但开启后要适配代码。我的做法是:在应用层做校验(如Java用@Size(max=32)),数据库层保持严格,双保险。
4.2 SELECT:从简单查询到执行计划分析
SELECT * FROM users;是入门,但真实世界需要精准打击:
SELECT id, username FROM users WHERE status = 1 ORDER BY created_at DESC LIMIT 20;
只查需要的字段(避免*),加WHERE过滤,ORDER BY排序,LIMIT分页。注意:LIMIT 20是取前20条,LIMIT 10,20是从第10条开始取20条(偏移分页,大数据量慎用)。SELECT COUNT(*) FROM users WHERE status = 1;
统计行数。COUNT(*)比COUNT(id)快,因为前者只数行,后者要读id字段。但COUNT(字段)会忽略NULL值,COUNT(*)不会。EXPLAIN SELECT * FROM users WHERE username = 'alice';
这是命令行里最重要的调试命令!它显示MySQL如何执行这条SQL:type列:const(主键等值查询,最快)、ref(普通索引查找)、ALL(全表扫描,要命!)key列:实际使用的索引名。如果是NULL,说明没走索引。rows列:预估扫描行数。如果比总行数还大,说明索引失效。
我处理过一个案例:WHERE email = ?总是慢,EXPLAIN发现key是NULL。查SHOW INDEX发现email字段没建索引!加ALTER TABLE users ADD INDEX idx_email (email);后,查询从2秒降到20毫秒。
4.3 UPDATE与DELETE:没有WHERE就是灾难
这两条命令最危险,因为不加WHERE条件会作用于全表。命令行里没有“确认弹窗”,敲回车就执行。
UPDATE users SET status = 0 WHERE id = 123;✅ 安全,有主键条件UPDATE users SET status = 0;❌ 危险!所有用户都被禁用
我的保命三原则:
- 永远先用SELECT验证WHERE条件:
SELECT id, username, status FROM users WHERE id = 123; -- 确认要改的是谁 UPDATE users SET status = 0 WHERE id = 123; -- 再执行 - 加LIMIT限制影响行数(MySQL特有):
UPDATE users SET status = 0 WHERE status = 1 LIMIT 1000;
即使条件写错,最多只改1000行,留出抢救时间。 - 在事务里执行:
START TRANSACTION; UPDATE users SET status = 0 WHERE id = 123; SELECT * FROM users WHERE id = 123; -- 立刻验证 COMMIT; -- 确认无误再提交 -- 或 ROLLBACK; -- 发现不对马上回滚
DELETE同理。DELETE FROM users WHERE id = 123;安全,DELETE FROM users;是删库跑路。物理删除不可逆,所以线上操作前,我必做mysqldump -u user -p db_name table_name > backup_$(date +%s).sql备份单表。
4.4 事务控制:BEGIN/COMMIT/ROLLBACK不是可选项
MySQL默认是自动提交(autocommit=1),每条SQL都是独立事务。但业务逻辑往往需要多步原子操作:
START TRANSACTION; INSERT INTO orders (user_id, amount) VALUES (123, 99.9); UPDATE users SET balance = balance - 99.9 WHERE id = 123; -- 如果第二步失败,整个订单不成立 COMMIT;关键点:
START TRANSACTION或BEGIN开启事务。COMMIT永久保存,ROLLBACK全部撤销。- 事务中所有操作都在内存缓冲区,不写磁盘,直到
COMMIT才刷盘(受innodb_flush_log_at_trx_commit参数控制)。 - 不要在事务里做耗时操作(如调用外部API),会持锁太久,阻塞其他连接。
我踩过的坑:在一个事务里先SELECT FOR UPDATE锁住库存行,然后调用支付网关接口。结果网关响应慢,锁持有了5秒,导致抢购请求排队。解决方案:把支付调用移到事务外,只在事务里做库存扣减和订单生成。
5. 实战排错:从ERROR 1062到慢查询,命令行里的救火现场
命令行最大的价值,不是建库建表,而是在问题发生时,给你最原始、最真实的线索。下面复现三个真实故障的完整排查过程。
5.1 ERROR 1062:重复键冲突,到底是哪里重复?
现象:执行INSERT INTO users (username) VALUES ('admin');报错:
ERROR 1062 (23000): Duplicate entry 'admin' for key 'username'新手第一反应是“用户名已存在”,但真相可能更复杂:
排查链路:
- 查
username字段的约束:SHOW CREATE TABLE users\G→ 发现username有UNIQUE KEY - 查重复值:
SELECT id, username FROM users WHERE username = 'admin';→ 返回两条记录?不可能,UNIQUE约束不允许。 - 检查字符集:
SELECT HEX(username) FROM users WHERE username = 'admin';→ 发现一个是61646D696E(admin),另一个是61646D696E20(admin+空格)。原来前端传参带了空格,MySQL默认忽略末尾空格比较,所以认为重复。 - 解决方案:插入前
TRIM(),或建表时用VARCHAR(自动去空格)。
注意:
UNIQUE约束对NULL值特殊处理——可以有多个NULL,因为NULL != NULL。所以INSERT INTO t(a) VALUES (NULL),(NULL);是合法的。
5.2 查询突然变慢:从SHOW PROCESSLIST到EXPLAIN
现象:某个报表查询从0.1秒涨到15秒,SELECT * FROM orders WHERE created_at > '2023-01-01';。
排查步骤:
SHOW PROCESSLIST;→ 发现该SQL状态是Sending data,且Time列显示12秒,确认是它在慢。EXPLAIN分析:
输出EXPLAIN SELECT * FROM orders WHERE created_at > '2023-01-01';type: ALL,rows: 2500000→ 全表扫描250万行!- 查索引:
SHOW INDEX FROM orders;→created_at字段没索引! - 加索引:
ALTER TABLE orders ADD INDEX idx_created_at (created_at); - 再
EXPLAIN→type: range,rows: 12000→ 扫描降为1.2万行,查询回到0.3秒。
延伸教训:时间范围查询,索引必须是(created_at)单列,或(created_at, status)联合索引(左前缀原则)。如果建(status, created_at),WHERE created_at > ?无法用上索引。
5.3 连接数爆满:Too many connections的根因定位
现象:新连接报错ERROR 1040 (HY000): Too many connections。
排查流程:
- 查当前连接数:
SHOW STATUS LIKE 'Threads_connected';→ 返回201,而max_connections=200。 - 查活跃连接:
SHOW PROCESSLIST;→ 发现198个连接状态是Sleep,Time列显示几百到几千秒。 - 查这些连接的来源:
SELECT user, host, time FROM information_schema.processlist WHERE command = 'Sleep' ORDER BY time DESC LIMIT 10;→ 发现全是app_user@10.0.1.%,time超3600秒。 - 根因:应用代码没关闭数据库连接,连接池泄漏。
- 临时解决:
KILL掉长时间Sleep的连接(KILL 12345;),或重启应用。 - 长期方案:应用层加连接超时,监控
Threads_connected指标。
提示:
SHOW VARIABLES LIKE 'max_connections';查上限,SHOW STATUS LIKE 'Threads_%';看实时连接状态(Threads_running是真正干活的,Threads_connected是总连接数)。
6. 生产级习惯:让命令行操作从“能用”到“可靠”
掌握语法只是起点,真正在生产环境游刃有余,靠的是细节习惯和防御性操作。
6.1 命令行配置文件:告别重复输入参数
每次登录都敲一长串参数?把配置写进~/.my.cnf:
[client] host=127.0.0.1 port=3306 user=app_user password=your_secure_password default-character-set=utf8mb4 [mysql] auto-rehash pager less[client]段对所有MySQL客户端生效(mysql、mysqldump等)[mysql]段只对mysql命令行客户端生效auto-rehash:启用表名、字段名自动补全(按Tab键)pager less:查询结果分页显示,长结果不再刷屏
安全警告:.my.cnf文件权限必须是600(chmod 600 ~/.my.cnf),否则mysql会拒绝读取,报错Warning: World-writable config file。这是MySQL的安全机制,防止密码泄露。
6.2 SQL脚本执行:从交互式到批处理
交互式敲命令适合调试,批量操作必须用脚本:
# 创建初始化脚本 init.sql CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4; USE myapp; CREATE TABLE IF NOT EXISTS users (...); # 执行脚本 mysql -u root -p < init.sql # 或登录后执行 mysql -u root -p -e "source /path/to/init.sql"关键技巧:
- 脚本里用
IF NOT EXISTS避免重复建库建表报错 - 大数据导入用
LOAD DATA INFILE比INSERT快10倍:LOAD DATA INFILE '/tmp/users.csv' INTO TABLE users FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'; - 导出数据用
SELECT ... INTO OUTFILE:SELECT * FROM users WHERE status=1 INTO OUTFILE '/tmp/active_users.csv' FIELDS TERMINATED BY ',';
6.3 日志与审计:谁在什么时候干了什么?
MySQL自带审计功能,但命令行里最轻量的是通用查询日志(general log):
SET GLOBAL general_log = 'ON'; SET GLOBAL log_output = 'TABLE'; -- 日志写入mysql.general_log表 -- 或 SET GLOBAL log_output = 'FILE'; -- 写入文件,需指定log_error然后SELECT * FROM mysql.general_log ORDER BY event_time DESC LIMIT 10;就能看到所有SQL。
注意:通用日志性能损耗大,只在排查问题时临时开启,问题解决后立即关闭SET GLOBAL general_log = 'OFF';。
更专业的审计用mysqlauditgrep工具(MySQL Enterprise Audit插件),但命令行基础玩家掌握通用日志已足够。
6.4 我的每日三查清单
在团队里,我要求新人每天晨会前执行这三行命令,养成肌肉记忆:
# 1. 查连接数,防泄漏 mysql -u admin -p -e "SHOW STATUS LIKE 'Threads_connected';" # 2. 查慢查询数量(需提前开启slow_query_log) mysql -u admin -p -e "SHOW GLOBAL STATUS LIKE 'Slow_queries';" # 3. 查最近的错误日志(需有文件权限) tail -n 20 /var/log/mysqld.log | grep -i "error\|warning"这三行不解决具体问题,但能让你对数据库健康度有即时感知。就像开车前看油表、水温、胎压——不是每次都有问题,但习惯能救命。
最后分享一个个人体会:命令行MySQL的熟练度,不体现在你能多快敲出CREATE TABLE,而在于当ERROR 1213 (40001): Deadlock found when trying to get lock报错时,你能否立刻想到SHOW ENGINE INNODB STATUS\G去抓死锁详情,然后根据LATEST DETECTED DEADLOCK段落,定位到是哪两条SQL在争抢同一行记录。这种“看到错误就知根因”的直觉,只能在命令行里,用一次次真实的报错和修复,一砖一瓦垒起来。