← 返回目录

第三章:数据操作

INSERT、UPDATE、DELETE 与批量操作

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 SELECTON 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 开销。