1. INSERT 插入数据
INSERT 语句用于向表中插入新行。MySQL 支持多种插入方式,从单行到批量、从简单到带冲突处理。
单行插入
-- 指定列名插入(推荐,清晰且不受列顺序影响)
INSERT INTO users (name, email, age) VALUES ('王五', 'wangwu@example.com', 25);
-- 省略列名(必须按表定义的列顺序提供所有值,不推荐)
INSERT INTO users VALUES (NULL, '赵六', 'zhaoliu@example.com', 30, NULL, NULL, NOW());
-- 查看插入结果
SELECT * FROM users WHERE name = '王五';
多行插入
一次 INSERT 可以插入多行数据,比逐条插入高效得多:
-- 批量插入多行
INSERT INTO users (name, email, age, city, salary) VALUES
('张三', 'zhangsan@example.com', 28, '北京', 15000.00),
('李四', 'lisi@example.com', 32, '上海', 20000.00),
('孙七', 'sunqi@example.com', 22, '深圳', 8000.00),
('周八', 'zhouba@example.com', 29, '上海', 18000.00),
('郑十', 'zhengshi@example.com', 27, '广州', 13000.00);
性能对比:批量插入比逐条插入效率高很多。插入 1000 行数据时,一次 INSERT 多行可能只需要几十毫秒,而执行 1000 次单行 INSERT 可能需要几秒甚至更久。原因是每条 SQL 都有网络传输、解析、事务开销。
INSERT ... SELECT
从一张表查询数据并插入另一张表:
-- 创建归档表
CREATE TABLE archive_users LIKE users;
-- 将年龄大于 60 的用户归档
INSERT INTO archive_users SELECT * FROM users WHERE age > 60;
-- 只复制部分列
INSERT INTO archive_users (name, email, age)
SELECT name, email, age FROM users WHERE created_at < '2025-01-01';
INSERT ... ON DUPLICATE KEY UPDATE(Upsert)
当插入的数据与唯一键(PRIMARY KEY 或 UNIQUE)冲突时,执行更新操作而不是报错:
-- 如果 email 已存在则更新 name 和 age,否则插入新行
INSERT INTO users (name, email, age) VALUES ('张三丰', 'zhangsan@example.com', 29)
ON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age);
-- MySQL 8.0.19+ 推荐使用 AS 别名语法
INSERT INTO users (name, email, age) VALUES ('张三丰', 'zhangsan@example.com', 29) AS new_data
ON DUPLICATE KEY UPDATE name = new_data.name, age = new_data.age;
-- 查看受影响行数:插入返回 1,更新返回 2,未变化返回 0
SELECT ROW_COUNT();
2. UPDATE 更新数据
UPDATE 语句用于修改表中已有行的数据。务必搭配 WHERE 子句使用,避免误更新所有行。
基本更新
-- 更新单个字段
UPDATE users SET age = 29 WHERE name = '张三';
-- 更新多个字段
UPDATE users SET age = 30, email = 'zhangsan_new@example.com' WHERE id = 1;
-- 使用表达式更新
UPDATE users SET salary = salary * 1.1 WHERE city = '北京';
-- 使用函数更新
UPDATE users SET name = TRIM(name), email = LOWER(email);
⚠️ 危险操作:UPDATE 不加 WHERE 会更新表中所有行!以下语句会将所有用户的年龄设为 0:
-- 危险!没有 WHERE 子句
UPDATE users SET age = 0; -- 所有用户的 age 都变成 0
条件更新
-- 使用 CASE WHEN 批量按条件更新
UPDATE users SET salary = CASE
WHEN city = '北京' THEN salary * 1.15
WHEN city = '上海' THEN salary * 1.10
WHEN city = '广州' THEN salary * 1.08
ELSE salary * 1.05
END;
-- 使用子查询条件
UPDATE users SET salary = salary + 2000
WHERE id IN (SELECT id FROM (SELECT id FROM users WHERE age > 30) AS tmp);
UPDATE 与 JOIN
MySQL 支持在 UPDATE 中使用 JOIN,根据另一张表的数据来更新:
-- 创建部门表
CREATE TABLE departments (
id INT AUTO_INCREMENT PRIMARY KEY,
city VARCHAR(50),
bonus DECIMAL(10, 2)
);
INSERT INTO departments (city, bonus) VALUES
('北京', 5000), ('上海', 4000), ('广州', 3000), ('深圳', 3500);
-- 根据部门表的 bonus 更新用户薪资
UPDATE users u
JOIN departments d ON u.city = d.city
SET u.salary = u.salary + d.bonus;
-- 使用 LEFT JOIN(未匹配到部门的用户不受影响)
UPDATE users u
LEFT JOIN departments d ON u.city = d.city
SET u.salary = u.salary + IFNULL(d.bonus, 0);
🔄 UPDATE JOIN 语法对比
| 数据库 | 语法 |
| MySQL | UPDATE t1 JOIN t2 ON ... SET t1.col = t2.val |
| PostgreSQL | UPDATE t1 SET col = t2.val FROM t2 WHERE t1.id = t2.id |
| SQL Server | UPDATE t1 SET col = t2.val FROM t1 JOIN t2 ON ... |
MySQL 使用标准 JOIN 语法直接写在 UPDATE 后,而 PostgreSQL 使用 FROM 子句,两种风格差异较大。
3. DELETE 删除数据
DELETE 语句用于从表中删除满足条件的行。和 UPDATE 一样,务必搭配 WHERE 子句使用。
基本删除
-- 删除指定行
DELETE FROM users WHERE id = 1;
-- 删除满足条件的行
DELETE FROM users WHERE age < 20;
-- 删除多个条件
DELETE FROM users WHERE city = '广州' AND salary < 10000;
使用子查询删除
-- 删除没有对应部门的用户
DELETE FROM users WHERE city NOT IN (SELECT city FROM departments);
-- MySQL 不允许在 DELETE 中直接引用正在删除的表,需要用子查询包装
DELETE FROM users WHERE id IN (
SELECT id FROM (
SELECT id FROM users WHERE salary < (SELECT AVG(salary) FROM users)
) AS tmp
);
DELETE 与 LIMIT
-- 配合 LIMIT 分批删除(避免大事务锁表)
DELETE FROM users WHERE created_at < '2024-01-01' LIMIT 1000;
-- 在脚本中循环执行,直到没有更多行要删除
-- 伪代码:
-- WHILE affected_rows > 0:
-- DELETE FROM users WHERE created_at < '2024-01-01' LIMIT 1000;
TRUNCATE TABLE vs DELETE
清空整张表有两种方式,它们的行为差异很大:
| 特性 | DELETE FROM table | TRUNCATE TABLE table |
|---|---|---|
| 速度 | 较慢(逐行删除) | 非常快(直接释放数据页) |
| 事务支持 | 支持回滚 | 不可回滚(DDL 操作) |
| WHERE 子句 | 支持 | 不支持 |
| AUTO_INCREMENT | 不重置 | 重置为 1 |
| 触发器 | 触发 DELETE 触发器 | 不触发 |
| 日志 | 记录每行删除 | 仅记录页释放 |
-- 清空表(可回滚,较慢)
DELETE FROM users;
-- 清空表(不可回滚,极快)
TRUNCATE TABLE users;
⚠️ 危险操作:DELETE 不加 WHERE 会删除所有行!TRUNCATE 更快但不可回滚,也不触发触发器。在执行删除操作前,建议先用 SELECT 确认即将删除的数据:
-- 先查看要删除的数据
SELECT * FROM users WHERE created_at < '2024-01-01';
-- 确认无误后再执行删除
DELETE FROM users WHERE created_at < '2024-01-01';
4. REPLACE 与 ON DUPLICATE KEY UPDATE
MySQL 提供两种处理"插入或更新"(Upsert)的方式。虽然功能类似,但内部行为差异很大。
REPLACE INTO
如果主键或唯一键冲突,先删除旧行再插入新行:
-- 如果 email 冲突,删除旧行,插入新行
REPLACE INTO users (name, email, age) VALUES ('张三丰', 'zhangsan@example.com', 29);
-- REPLACE 也支持多行
REPLACE INTO users (name, email, age) VALUES
('张三丰', 'zhangsan@example.com', 29),
('新用户', 'newuser@example.com', 20);
两者的关键区别
| 特性 | REPLACE INTO | ON DUPLICATE KEY UPDATE |
|---|---|---|
| 冲突处理 | DELETE + INSERT | UPDATE 原有行 |
| AUTO_INCREMENT | 生成新 ID(旧 ID 丢失) | 保留原 ID |
| 未指定列 | 重置为默认值 | 保留原值 |
| 触发器 | 触发 DELETE + INSERT | 触发 INSERT 或 UPDATE |
| 外键关联 | 可能级联删除关联数据 | 安全,不影响关联数据 |
-- 推荐:使用 ON DUPLICATE KEY UPDATE(更安全)
INSERT INTO users (name, email, age, salary) VALUES ('张三', 'zhangsan@example.com', 30, 16000)
ON DUPLICATE KEY UPDATE age = VALUES(age), salary = VALUES(salary);
-- 不推荐:REPLACE 会丢失未指定列的数据
-- 以下语句会让 city、salary 等未指定列重置为默认值
REPLACE INTO users (name, email, age) VALUES ('张三', 'zhangsan@example.com', 30);
最佳实践:绝大多数场景下推荐使用 INSERT ... ON DUPLICATE KEY UPDATE 而非 REPLACE。REPLACE 的"删除再插入"行为会改变自增 ID、重置未指定的列、触发级联删除,容易造成数据丢失。
5. 安全操作建议
数据操作(尤其是 UPDATE 和 DELETE)直接修改或删除数据,一旦出错可能造成严重后果。以下是保护数据的关键实践。
始终使用 WHERE 子句
-- ❌ 危险:更新所有行
UPDATE users SET salary = 0;
-- ✅ 安全:指定条件
UPDATE users SET salary = 0 WHERE id = 5;
-- ❌ 危险:删除所有行
DELETE FROM users;
-- ✅ 安全:指定条件
DELETE FROM users WHERE id = 5;
使用事务保护重要操作
-- 开启事务
START TRANSACTION;
-- 执行操作
UPDATE users SET salary = salary - 5000 WHERE id = 1;
UPDATE users SET salary = salary + 5000 WHERE id = 2;
-- 检查结果
SELECT * FROM users WHERE id IN (1, 2);
-- 确认无误后提交
COMMIT;
-- 如果发现错误,回滚所有操作
-- ROLLBACK;
操作前备份
-- 创建备份表
CREATE TABLE users_backup_20260416 AS SELECT * FROM users;
-- 或者使用 mysqldump 命令行工具备份
-- mysqldump -u root -p mydb users > users_backup.sql
先 SELECT 再操作
-- 先查看即将受影响的数据
SELECT * FROM users WHERE created_at < '2024-01-01';
-- 确认数据范围正确后,将 SELECT 改为 DELETE
DELETE FROM users WHERE created_at < '2024-01-01';
使用 LIMIT 测试
-- 先更新一条测试
UPDATE users SET salary = salary * 1.1 WHERE city = '北京' LIMIT 1;
-- 检查结果
SELECT * FROM users WHERE city = '北京';
-- 确认无误后去掉 LIMIT
UPDATE users SET salary = salary * 1.1 WHERE city = '北京';
启用安全模式
-- 开启安全更新模式
SET sql_safe_updates = 1;
-- 开启后,以下语句会报错(没有 WHERE 或 WHERE 未使用索引列)
UPDATE users SET age = 0; -- ERROR 1175
DELETE FROM users; -- ERROR 1175
-- 使用索引列的 WHERE 条件才能执行
UPDATE users SET age = 0 WHERE id = 1; -- OK
-- 临时关闭安全模式
SET sql_safe_updates = 0;
安全检查清单:
- • 每条 UPDATE/DELETE 都有 WHERE 子句
- • 重要操作放在事务中,确认后再 COMMIT
- • 批量操作前先备份相关数据
- • 先用 SELECT 预览受影响的行
- • 用 LIMIT 小范围测试后再执行完整操作
- • 开发环境启用
sql_safe_updates = 1
6. 本章要点
➕ INSERT 插入
支持单行、多行、INSERT SELECT 和 ON DUPLICATE KEY UPDATE。批量插入比逐条插入效率高,推荐指定列名。
✏️ UPDATE 更新
支持表达式更新、CASE WHEN 条件更新和 JOIN 跨表更新。务必加 WHERE 子句,避免全表更新。
🗑️ DELETE 删除
DELETE 逐行删除可回滚,TRUNCATE 快速清空不可回滚。大数据量用 LIMIT 分批删除避免锁表。
🔄 Upsert 操作
推荐 ON DUPLICATE KEY UPDATE 而非 REPLACE。REPLACE 会删除再插入,可能丢失数据和改变 ID。
🛡️ 安全操作
使用事务保护关键操作,操作前备份数据,先 SELECT 再修改,启用 sql_safe_updates 防止误操作。
⚡ 性能技巧
批量插入代替逐条插入,大量删除用 LIMIT 分批执行,INSERT SELECT 高效复制数据,事务合并减少 IO 开销。