MySQL 是 Web 应用里最常打交道的数据库。它的坑大多不在语法,而在配置和设计:字符集选错存不了 emoji、金额用了
FLOAT、GROUP BY突然报错、锁等待查不出是谁卡着。这篇从安装配置讲到索引优化、事务锁、慢查询和主从复制。命令行客户端工具部分见 MySQL 命令行使用教程,SQL 语法见 SQL 命令使用教程。
一、安装与初始化
1.1 安装
# Ubuntu / Debian
sudo apt update && sudo apt install mysql-server
# CentOS / RHEL(8 以后官方仓库是 mysql-server,需先配好源)
sudo dnf install mysql-server
# macOS
brew install mysqlWindows 从 官方下载页取安装包。建议直接上 8.0——5.7 已停止维护,且 8.0 的性能和功能(窗口函数、CTE、JSON 增强)提升明显。
1.2 初始化与安全加固
sudo mysql_secure_installation # 设置 root 密码、删匿名用户、禁远程 root
sudo systemctl enable --now mysqlmysql_secure_installation 会问几件事:是否设置 root 密码、是否删除匿名账号、是否禁止 root 远程登录、是否删除 test 库。建议全部选是。
1.3 配置文件
MySQL 按顺序读 /etc/my.cnf → /etc/mysql/my.cnf → ~/.my.cnf,后面的覆盖前面的。
[mysqld]
datadir = /var/lib/mysql
socket = /var/run/mysqld/mysqld.sock
log-error = /var/log/mysql/error.log
# 字符集:必须用 utf8mb4
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# InnoDB 核心参数
innodb_buffer_pool_size = 4G # 建议物理内存的 50%-70%
innodb_log_file_size = 512M # 重做日志大小,影响写入吞吐
innodb_flush_log_at_trx_commit = 1 # 1=最安全,2=更快但崩溃可能丢 1 秒
max_connections = 200MySQL 8.0 重要变更:查询缓存(
query_cache_size/query_cache_type)已被彻底移除。老教程里几乎人人都写query_cache_size=128M,在 8.0 上会导致启动失败(未知变量)。8.0 不再有查询缓存这一层,优化重心转向innodb_buffer_pool_size和索引设计。
1.4 字符集:为什么必须 utf8mb4
MySQL 里有个历史遗留的坑:utf8 其实是 utf8mb3,每个字符最多 3 字节,存不了 emoji 和部分生僻汉字。真正的 UTF-8 是 utf8mb4(最多 4 字节)。
-- 查看当前字符集
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';建库时必须显式指定:
CREATE DATABASE company_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;如果表已经建成 utf8mb3,想插入 emoji 会直接报错(Incorrect string value)。改起来也麻烦,要 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,大表还可能锁很久。所以一开始就用 utf8mb4。
utf8mb4_unicode_ci和utf8mb4_0900_ai_ci(8.0 默认)都是大小写不敏感的排序规则。它们的区别在于 Unicode 排序算法版本,0900_ai_ci更新更准。新项目用 8.0 默认的就行。
二、数据类型选择
选类型是设计阶段最容易埋雷的地方。
| 用途 | 推荐类型 | 说明 |
|---|---|---|
| 主键 | BIGINT UNSIGNED AUTO_INCREMENT | 别用 INT,容易用完(21 亿) |
| 金额 | DECIMAL(10,2) | 绝不用 FLOAT/DOUBLE |
| 短字符串 | VARCHAR(n) | 按实际需要设 n |
| 长文本 | TEXT | 别在 TEXT 上建索引 |
| 布尔 | TINYINT(1) | MySQL 没有独立布尔类型 |
| 时间戳 | TIMESTAMP / DATETIME | 见下方说明 |
| 枚举 | TINYINT + 应用层字典 | 少用 ENUM |
| JSON | JSON | 5.7+,可索引生成列 |
关于金额:FLOAT/DOUBLE 是二进制浮点,存在精度误差,0.1 + 0.2 不等于 0.3。金额、价格、任何需要精确十进制运算的场景,一律用 DECIMAL。
salary DECIMAL(10,2) -- ✅ 精确,10 位总长、2 位小数
salary FLOAT -- ❌ 浮点误差,金额不可用关于时间:
DATETIME存"字面时间",不做时区转换(范围 1000-9999 年)TIMESTAMP存"时间点",写入时按会话时区转换,以 UTC 存储(范围 1970-2038 年,有 2038 问题)- 跨时区的系统用
TIMESTAMP;只在本时区、且想要更大范围用DATETIME
关于 VARCHAR(n) 的 n:它限制的是字符数而非字节数(utf8mb4 下 VARCHAR(100) 最多占 400 字节)。行内所有 VARCHAR 加起来别超过约 65535 字节,否则建表失败。
三、建表与约束
CREATE TABLE employees (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
department VARCHAR(50),
salary DECIMAL(10,2) NOT NULL DEFAULT 0,
hire_date DATE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;几个值得说明的点:
ENGINE=InnoDB:8.0 起是默认引擎,支持事务和行锁。别用 MyISAM(不支持事务,表级锁)。DEFAULT CURRENT_TIMESTAMP:TIMESTAMP支持;DATE的默认值想用表达式需要 8.0.13+ 且要加括号:DEFAULT (CURRENT_DATE)。ON UPDATE CURRENT_TIMESTAMP:更新行时自动刷新时间,比应用层维护省事。CHECK约束:MySQL 8.0.16+ 才真正强制,之前的版本会接受但不检查。
修改表结构:
ALTER TABLE employees ADD COLUMN phone VARCHAR(20);
ALTER TABLE employees MODIFY COLUMN phone VARCHAR(30); -- 改类型(MySQL 专有)
ALTER TABLE employees CHANGE COLUMN phone mobile VARCHAR(30); -- 改名 + 改类型
ALTER TABLE employees DROP COLUMN mobile;
MODIFY和CHANGE都是 MySQL 专有语法。PostgreSQL 用ALTER COLUMN ... TYPE,标准 SQL 也是那一套。写可移植的 SQL 要注意这点。
四、数据操作
4.1 插入
-- 单条
INSERT INTO employees (name, email, department, salary)
VALUES ('张三', 'zhang@company.com', '技术部', 15000);
-- 批量(一次多行比循环单行快得多)
INSERT INTO employees (name, email, department, salary) VALUES
('李四', 'li@company.com', '市场部', 12000),
('王五', 'wang@company.com', '财务部', 13000);
-- 从查询结果插入
INSERT INTO employees_archive (name, email)
SELECT name, email FROM employees WHERE hire_date < '2020-01-01';4.2 UPSERT:存在就更新
INSERT INTO employees (id, name, salary)
VALUES (1, '张三', 16000)
ON DUPLICATE KEY UPDATE salary = VALUES(salary);冲突(主键或唯一键重复)时执行 UPDATE。这是 MySQL 的写法;PostgreSQL 和 SQLite 用 ON CONFLICT ... DO UPDATE。
REPLACE INTO employees (id, name, salary) VALUES (1, '张三', 16000);REPLACE INTO 是先删后插(先删冲突行,再插入),会触发 ON DELETE 级联和触发器、且会换掉自增主键。除非你确实想要"删旧插新",否则优先用 ON DUPLICATE KEY UPDATE。
4.3 查询、更新、删除
SELECT name, salary FROM employees
WHERE department = '技术部' AND salary > 14000
ORDER BY hire_date DESC
LIMIT 5 OFFSET 0;
UPDATE employees SET salary = salary * 1.1 WHERE id = 1;
DELETE FROM employees WHERE id = 5;五、查询进阶
5.1 连接
-- 假设 employees 有 department_id 列指向 departments.id
SELECT e.name, e.salary, d.name AS dept_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
-- 左连接:所有员工,没有部门的也列出
SELECT e.name, d.name AS dept_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
-- 找出没有部门的员工(反连接)
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
WHERE d.id IS NULL;上面三个例子都要求
employees表真的有department_id列。很多示例代码会忘记这一步——只建了department VARCHAR(50)就直接JOIN ... ON e.department_id,跑起来报Unknown column。设计时想清楚:部门是字符串还是外键引用?
5.2 GROUP BY 与 ONLY_FULL_GROUP_BY
SELECT department, COUNT(*) AS cnt, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING COUNT(*) > 3
ORDER BY cnt DESC;MySQL 5.7 起默认开启 ONLY_FULL_GROUP_BY,导致老代码里这种写法会报错:
-- ❌ 报错:name 不在 GROUP BY 里,也不是聚合函数
SELECT department, name, COUNT(*) FROM employees GROUP BY department;这个模式的目的是防止"选了不在分组里的列,值又不确定"的歧义查询。正确做法是用聚合函数(GROUP_CONCAT(name)、MAX(name))或者把列加进 GROUP BY。查当前设置:
SELECT @@sql_mode;5.3 窗口函数(MySQL 8.0+)
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank_skip
FROM employees;窗口函数能在不合并行的前提下做分组内排名、移动平均、同比环比。8.0 之前只能用变量或自连接绕,很难写也很难优化。
5.4 CTE(MySQL 8.0+)
WITH dept_stats AS (
SELECT department, COUNT(*) AS cnt, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT d.name, s.cnt, s.avg_salary
FROM dept_stats s
JOIN departments d ON d.name = s.department
WHERE s.cnt > 3;递归 CTE 可以遍历树形结构:
WITH RECURSIVE org AS (
SELECT id, name, manager_id, 1 AS level FROM employees WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, o.level + 1
FROM employees e JOIN org o ON e.manager_id = o.id
)
SELECT * FROM org ORDER BY level;递归 CTE 默认最大递归深度受
cte_max_recursion_depth限制(默认 1000),层级很深的树要调整这个变量。
5.5 JSON 字段(5.7+)
CREATE TABLE events (id BIGINT PRIMARY KEY, payload JSON);
INSERT INTO events VALUES (1, '{"user": "张三", "action": "login", "ip": "1.2.3.4"}');
-- 取值
SELECT JSON_EXTRACT(payload, '$.user') AS user FROM events;
SELECT payload->>'$.user' AS user FROM events; -- 简写,返回文本
SELECT payload->'$.user' AS user FROM events; -- 简写,返回 JSON
-- 条件查询
SELECT * FROM events WHERE JSON_EXTRACT(payload, '$.action') = 'login';
-- 生成列 + 索引(JSON 字段本身不能建索引,要借助生成列)
ALTER TABLE events
ADD COLUMN action VARCHAR(20) AS (JSON_UNQUOTE(payload->>'$.action')) STORED,
ADD INDEX idx_action (action);JSON 字段适合"结构不定"的数据,但不适合替代关系建模。能用普通列表达的,就别塞 JSON——查询和索引都更麻烦。
六、索引与优化
6.1 索引类型与创建
CREATE INDEX idx_dept_salary ON employees(department, salary); -- 复合索引
CREATE UNIQUE INDEX uidx_email ON employees(email); -- 唯一索引
CREATE INDEX idx_name_prefix ON employees(name(10)); -- 前缀索引(长字符串)
SHOW INDEX FROM employees; -- 查看
DROP INDEX idx_dept_salary ON employees; -- 删除(MySQL 要带 ON)6.2 最左前缀原则
复合索引 (department, salary) 能加速:
WHERE department = ?WHERE department = ? AND salary > ?
但不能加速:
WHERE salary > ?(跳过了最左列)
建复合索引时,把区分度高、最常单独查询的列放最左。
6.3 索引失效的典型场景
-- ❌ 对索引列做运算/函数 → 索引失效,全表扫描
SELECT * FROM employees WHERE YEAR(hire_date) = 2023;
-- ✅ 改成范围比较
SELECT * FROM employees
WHERE hire_date >= '2023-01-01' AND hire_date < '2024-01-01';
-- ❌ 前导通配符
SELECT * FROM employees WHERE name LIKE '%三';
-- ❌ 隐式类型转换(phone 是 VARCHAR,传了数字)
SELECT * FROM employees WHERE phone = 13800138000;
-- ❌ OR 连接了未索引的列(可能退化为全表扫描)
SELECT * FROM employees WHERE email = 'x@y.com' OR department = '技术部';6.4 EXPLAIN 看执行计划
EXPLAIN SELECT * FROM employees WHERE department = '技术部' AND salary > 14000;
-- 8.0.18+:实际执行并给真实耗时
EXPLAIN ANALYZE SELECT * FROM employees WHERE department = '技术部';重点看这两列:
type:访问类型,从好到坏大致是system>const>eq_ref>ref>range>index>ALL。看到ALL就是全表扫描。rows:预估扫描行数。数字很大说明索引没起作用。key:实际用了哪个索引。为 NULL 说明没用索引。
EXPLAIN ANALYZE 会真的执行查询(注意别在写查询上用),输出各步骤的实际耗时,定位慢点比 EXPLAIN 准确。
6.5 慢查询日志
排查性能问题的第一站。
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'slow_query_log_file';
-- 动态开启(重启失效,要持久化写进 my.cnf)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过 1 秒的记录[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1 # 连未走索引的也记(日志会变多)日志里的查询可以用 mysqldumpslow 或 pt-query-digest(Percona Toolkit)聚合分析,找出最耗时的几条。
6.6 OPTIMIZE 与 ANALYZE
OPTIMIZE TABLE employees; # 整理碎片、回收空间(大表耗时且会锁表,慎用)
ANALYZE TABLE employees; # 更新索引统计信息,帮助优化器选对索引七、事务与锁
7.1 事务
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- 失败时 ROLLBACKInnoDB 才支持事务(MyISAM 不支持)。
7.2 隔离级别
SELECT @@transaction_isolation; -- 查看(8.0)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 设置| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 |
READ COMMITTED | 否 | 可能 | 可能 |
REPEATABLE READ | 否 | 否 | InnoDB 基本避免 |
SERIALIZABLE | 否 | 否 | 否 |
MySQL InnoDB 默认是 REPEATABLE READ,而 PostgreSQL 默认是 READ COMMITTED。这个差异会让同样的并发代码在两个库上表现不同——写与并发相关的逻辑时必须确认。
7.3 锁
-- 悲观锁:锁定读到的行,直到事务结束
SELECT * FROM orders WHERE id = 100 FOR UPDATE;
-- 共享锁(只读锁定,其他事务可读不可写)
SELECT * FROM orders WHERE id = 100 LOCK IN SHARE MODE;InnoDB 的锁粒度:
- 行锁:锁定索引记录。注意:如果
WHERE没走索引,行锁会退化成锁全表(因为要扫描所有行)。 - 间隙锁(Gap Lock):
REPEATABLE READ下会锁住索引记录之间的"间隙",防止其他事务插入——这是 InnoDB 避免幻读的机制,也是死锁的常见来源。 - 表锁:DDL 操作、或显式
LOCK TABLES。
查看锁等待:
-- 当前锁等待情况(8.0)
SELECT * FROM performance_schema.data_lock_waits;
-- 正在运行的事务
SELECT * FROM information_schema.innodb_trx\G7.4 死锁
死锁是两个事务互相持有对方需要的锁。InnoDB 会自动检测并回滚其中一个(报 ERROR 1213: Deadlock found)。
SHOW ENGINE INNODB STATUS\G输出里的 LATEST DETECTED DEADLOCK 段会给出最近一次死锁的两个事务和各自持有的锁,是排查死锁的第一手资料。
减少死锁的经验:
- 多个事务按相同顺序访问表和行(比如都按
id升序更新) - 事务尽量短,别在事务里做网络调用/耗时计算
- 用合适的索引,避免行锁退化成表级/间隙锁
锁等待超时由 innodb_lock_wait_timeout 控制(默认 50 秒):
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
SET GLOBAL innodb_lock_wait_timeout = 30;八、用户与权限
CREATE USER 'app'@'localhost' IDENTIFIED BY 'StrongPass!2026';
GRANT SELECT, INSERT, UPDATE, DELETE ON company_db.* TO 'app'@'localhost';
SHOW GRANTS FOR 'app'@'localhost';
REVOKE DELETE ON company_db.* FROM 'app'@'localhost';
DROP USER 'app'@'%';'user'@'host' 是 MySQL 的特色:同一用户名从不同主机连,是不同账号。'app'@'localhost' 和 'app'@'%'(任意主机)需要分别授权。
别给应用账号
ALL PRIVILEGES ON *.*。最小权限原则——应用只需要它那几个库的 CRUD,就不该给它DROP DATABASE的能力。
九、备份与恢复
9.1 逻辑备份(mysqldump)
# 生产必备参数
mysqldump -u root -p \
--single-transaction \
--routines --triggers --events \
--set-gtid-purged=OFF \
company_db | gzip > company_db-$(date +%F).sql.gz--single-transaction 对 InnoDB 表建立一致性快照,不锁表,备份期间业务照常写入。
# 恢复
gunzip < company_db-2026-09-22.sql.gz | mysql -u root -p company_db9.2 物理备份(XtraBackup)
mysqldump 是逻辑备份(导出 SQL 文本),库大了会很慢(几十 GB 以上不实用)。生产环境的大库用物理备份工具 Percona XtraBackup,直接复制数据文件,支持热备和增量。
9.3 定时备份脚本
#!/bin/bash
set -euo pipefail
BACKUP_DIR=/backups/mysql
mkdir -p "$BACKUP_DIR"
DATE=$(date +%F_%H%M)
# 用 --login-path 避免在脚本/进程列表里暴露密码
mysqldump --login-path=backup \
--single-transaction --routines --triggers \
--all-databases | gzip > "$BACKUP_DIR/all-$DATE.sql.gz"
find "$BACKUP_DIR" -name 'all-*.sql.gz' -mtime +7 -delete# 每天凌晨 2 点
0 2 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1别在 crontab 里写
-pPASSWORD。crontab 文件、ps aux输出都可能被别人看到明文密码。用mysql_config_editor生成登录路径(见 MySQL 命令行教程 §1.3)。
十、存储过程、函数、触发器、事件
10.1 存储过程
DELIMITER //
CREATE PROCEDURE IncreaseSalaries(IN dept VARCHAR(50), IN pct FLOAT)
BEGIN
UPDATE employees
SET salary = salary * (1 + pct / 100)
WHERE department = dept;
END //
DELIMITER ;
CALL IncreaseSalaries('技术部', 10);DELIMITER // 是客户端命令(不是 SQL),用来临时把语句结束符从 ; 改成 //,否则过程体里的分号会提前结束 CREATE PROCEDURE。
参数名别和列名重名。如果参数叫
department,过程体里的WHERE department = department会变成自己和自己比,永远为真(或产生歧义)。上面的例子用dept避开就是这个原因。
10.2 函数
DELIMITER //
CREATE FUNCTION GetEmployeeCount(dept VARCHAR(50))
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE cnt INT;
SELECT COUNT(*) INTO cnt FROM employees WHERE department = dept;
RETURN cnt;
END //
DELIMITER ;
SELECT GetEmployeeCount('市场部');DETERMINISTIC 告诉优化器"同样输入总是同样输出",否则在开启 binlog 的服务器上创建函数会被拒绝。
10.3 触发器
CREATE TABLE audit_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
action VARCHAR(20),
table_name VARCHAR(50),
record_id BIGINT,
change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
DELIMITER //
CREATE TRIGGER trg_employee_after_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, table_name, record_id)
VALUES ('UPDATE', 'employees', NEW.id);
END //
DELIMITER ;触发器里可以引用 NEW(新行)和 OLD(旧行)。触发器逻辑对应用透明,容易变成"隐形业务逻辑",能用应用层做的事就别塞触发器。
10.4 事件调度
SET GLOBAL event_scheduler = ON;
CREATE EVENT daily_audit_cleanup
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP
DO
DELETE FROM audit_log WHERE change_time < NOW() - INTERVAL 30 DAY;事件相当于数据库内置的定时任务。注意 event_scheduler 默认是 OFF,不打开的话事件不会执行。
十一、主从复制(企业级)
单机 MySQL 有单点风险。生产环境通常配一主一从(或多从),从库可以做读负载分担和故障切换。
主库配置:
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog_format = ROW # 推荐 ROW,复制更精确
gtid_mode = ON
enforce_gtid_consistency = ONCREATE USER 'repl'@'%' IDENTIFIED BY 'ReplPass!2026';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';从库配置:
[mysqld]
server-id = 2
relay-log = relay-bin
read_only = ON # 从库只读(超级用户仍可写)
gtid_mode = ON
enforce_gtid_consistency = ONCHANGE REPLICATION SOURCE TO
SOURCE_HOST = '10.0.0.1',
SOURCE_USER = 'repl',
SOURCE_PASSWORD = 'ReplPass!2026',
SOURCE_AUTO_POSITION = 1; -- 用 GTID 自动定位
START REPLICA;
SHOW REPLICA STATUS\G -- 看 Replica_IO_Running / Replica_SQL_Running 是否都为 Yes8.0 术语变更:
CHANGE MASTER TO→CHANGE REPLICATION SOURCE TO,START SLAVE→START REPLICA,SHOW SLAVE STATUS→SHOW REPLICA STATUS。老命令还能用,但已废弃。
主从延迟是常见问题:SHOW REPLICA STATUS 里的 Seconds_Behind_Source 反映了从库落后主库多少秒。大事务、单线程回放、从库负载高都会加剧延迟。
十二、Python 集成
用官方驱动 mysql-connector-python(pip install mysql-connector-python):
import mysql.connector
from mysql.connector import Error
connection = None
try:
connection = mysql.connector.connect(
host='localhost',
user='python_user',
password='secure_pass',
database='company_db',
charset='utf8mb4',
)
if connection.is_connected():
cursor = connection.cursor()
# 参数化查询:%s 是占位符(注意不是 ?)
cursor.execute(
"SELECT id, name, salary FROM employees WHERE department = %s",
('技术部',),
)
for emp_id, name, salary in cursor.fetchall():
print(f"{emp_id}\t{name}\t{salary}")
# 插入
cursor.execute(
"INSERT INTO employees (name, email, department, salary) VALUES (%s, %s, %s, %s)",
('钱七', 'qian@company.com', '人事部', 11000),
)
connection.commit()
cursor.close()
except Error as e:
print("数据库错误:", e)
finally:
if connection and connection.is_connected():
connection.close()几个要点:
%s是 MySQL 驱动的占位符(不是?,那是 SQLite 的)。它同时处理了转义,是防注入的正道。charset='utf8mb4'显式指定,避免连接层用错字符集导致中文乱码。- 连接要
close()。生产代码用连接池(如mysql-connector-python的pooling或 SQLAlchemy),别每次请求新建连接。 autocommit默认是关的。增删改之后必须commit(),否则不生效。
十三、常见问题排查
13.1 忘记 root 密码
# 1. 停服务
sudo systemctl stop mysql
# 2. 跳过权限表启动(--skip-networking 必须加,否则这段窗口任何人可免密进入)
sudo mysqld_safe --skip-grant-tables --skip-networking &-- 3. 免密登录,先刷权限再用 ALTER USER(跳过权限表时权限表在内存里是空的)
mysql -u root
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewStrongPass!2026';# 4. 关掉临时实例,正常启动
mysqladmin -u root -p shutdown
sudo systemctl start mysql13.2 错误码速查
| 错误码 | 含义 | 排查方向 |
|---|---|---|
1045 | Access denied | 用户名/密码错,或该用户不允许从当前主机连接 |
1049 | Unknown database | 库名写错 |
1054 | Unknown column | 列名写错,或表结构和你以为的不一样 |
1213 | Deadlock found | 死锁,InnoDB 已回滚一个事务 |
1205 | Lock wait timeout | 有长事务持锁,查 data_lock_waits |
2002 | Socket 连接失败 | socket 路径不对,或服务没起 |
2003 | 无法连接 | 网络/端口/防火墙,或 bind-address=127.0.0.1 限制 |
2013 | Lost connection during query | 查询太久被杀(max_execution_time/网络中断/服务重启) |
1055 | ONLY_FULL_GROUP_BY 报错 | 见 §5.2 |
13.3 连接数打满
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';
SHOW VARIABLES LIKE 'max_connections';
SELECT user, host, COUNT(*) FROM information_schema.processlist GROUP BY user, host;Threads_running 高说明有慢查询堆积(不是连接数问题);Threads_connected 逼近 max_connections 说明有连接泄漏(应用没关连接)或真的需要扩容。
学习资源
- MySQL 8.0 官方文档 —— 最权威,尤其看 “Server Administration” 和 “Optimization” 章节
- MySQL 官方博客 —— 版本变更(如
query_cache移除)的权威说明 - Use The Index, Luke —— 索引与 SQL 性能的免费经典
- Percona Toolkit —— 慢查询分析、在线 DDL、主从校验等运维利器
- MySQL Tutorial —— 入门教程
graph TD
A[安装与初始化] --> B[字符集 utf8mb4]
B --> C[设计表与选型]
C --> D[CRUD 与查询]
D --> E[索引与执行计划]
E --> F[事务与锁]
F --> G[慢查询与优化]
G --> H[备份恢复]
H --> I[主从复制]
I --> J[应用集成]MySQL 上手快,但真正拉开水平差距的是设计和配置:字符集一次选对、金额用
DECIMAL、索引想清楚最左前缀、事务写短一点。这几点做到了,大部分线上问题就不会找到你头上。
参考&致谢
系列教程
数据库系列
- SQL 命令使用教程:从入门到精通 —— SQL 标准命令,含 MySQL/PostgreSQL/SQLite 方言对照
- SQLite 使用全面教程:轻量级数据库的终极指南 —— 嵌入式数据库,类型系统、外键陷阱、WAL、FTS5
- MySQL 命令行使用全面教程:从入门到精通 ——
mysql客户端工具,批处理、备份、性能诊断 - MySQL 使用全面指南:从入门到高级实践 —— MySQL 安装配置、字符集、索引、事务锁、复制
- PostgreSQL 使用全面指南:从入门到企业级应用 —— PG 认证配置、JSONB、分区、备份与高可用
- PostgreSQL 命令行使用教程:掌握 psql 工具 ——
psql元命令、脚本化、\copy与pg_dump - PostgreSQL 实现原理深度剖析:高性能数据库引擎的核心机制 —— MVCC、WAL、B-tree 内核机制与可验证观察方法

