7、数据更新与删除:UPDATE语句、DELETE语句、条件更新与删除、事务保护
好,咱们今天聊点实在的。数据库里光能查数据、插数据,那只是基本功。真正干活的时候,改数据和删数据才是家常便饭。你想想看,一个订单状态从「待支付」变成「已支付」,这不就是UPDATE吗?用户要注销账号,这不就是DELETE吗?
但这里头坑不少。我见过太多新手,一条DELETE下去,整张表清空了,然后对着屏幕发呆。嗯,咱们今天就把这些操作掰开揉碎了讲清楚。
7.1 UPDATE语句:改数据的基本功
UPDATE的语法其实很简单,核心就三部分:你要改哪张表、你要改哪些字段、你要改哪些行。
UPDATE 表名
SET 字段1 = 新值1, 字段2 = 新值2
WHERE 条件;
我在项目中遇到过最典型的场景:用户修改个人信息。比如改手机号:
UPDATE users
SET phone = '13800138000', update_time = datetime('now')
WHERE user_id = 1001;
这里有个细节我要强调一下——SET子句里可以写表达式。比如给所有商品涨价10%:
UPDATE products
SET price = price * 1.1
WHERE category = '电子产品';
你看,price = price * 1.1,这种写法在SQL里完全合法。我第一次看到时还愣了一下,心想这也能行?后来发现,这其实是UPDATE最常用的技巧之一。
7.2 DELETE语句:删数据要三思
DELETE的语法比UPDATE还简单:
DELETE FROM 表名 WHERE 条件;
但越简单的东西,越容易出事。我曾经有个同事,在测试环境执行了一条:
DELETE FROM orders;
然后整个测试库的订单数据全没了。他当时脸都绿了。还好是测试环境,有备份。但如果是生产环境呢?
所以我的习惯是:写DELETE之前,先用SELECT查一遍。比如你想删掉所有一年未登录的僵尸用户:
-- 先查,确认是哪些数据
SELECT user_id, username, last_login
FROM users
WHERE last_login < date('now', '-1 year');
-- 确认无误后,再删
DELETE FROM users
WHERE last_login < date('now', '-1 year');
说白了,这就是「先看后删」原则。你多花10秒钟确认,可能就省了后面10个小时的恢复工作。
7.3 条件更新与删除:精确打击
实际业务中,很少有无条件更新或删除的场景。条件写得越精确,出错的概率越低。
常见的条件写法包括:
- 等值条件:WHERE status = 'inactive'
- 范围条件:WHERE price BETWEEN 100 AND 500
- 模糊匹配:WHERE email LIKE '%@spam.com'
- 复合条件:WHERE age > 60 AND level < 5
- 子查询条件:WHERE user_id IN (SELECT user_id FROM blacklist)
我印象最深的一个案例:有一次需要清理一批垃圾评论,条件是「用户已被封禁且评论内容包含广告链接」。当时我写的SQL是这样的:
DELETE FROM comments
WHERE user_id IN (
SELECT user_id FROM users WHERE status = 'banned'
)
AND content LIKE '%http%'
AND created_at < '2024-01-01';
三个条件叠加,精准命中目标数据。既不会误删正常用户的评论,也不会漏掉该删的垃圾数据。
7.4 事务保护:你的后悔药
说到事务,我得先问一句:你知道为什么要有事务吗?
说白了,事务就是给你一个「反悔」的机会。你执行了一堆UPDATE和DELETE,发现搞错了,只要还没提交,就能全部回滚。
在SQLite里,事务的基本用法是这样的:
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 如果一切正常
COMMIT;
-- 如果发现错了
-- ROLLBACK;
这个例子是转账操作。扣钱和加钱必须同时成功,或者同时失败。不能出现扣了钱但没加上的情况。这就是事务的原子性。
我在项目中遇到过更复杂的场景:批量删除用户数据时,需要同时清理用户表、订单表、评论表、日志表。如果其中一步失败,前面删掉的数据就找不回来了。这时候事务就是救命稻草:
BEGIN TRANSACTION;
DELETE FROM user_logs WHERE user_id = 1001;
DELETE FROM comments WHERE user_id = 1001;
DELETE FROM orders WHERE user_id = 1001;
DELETE FROM users WHERE user_id = 1001;
-- 检查每步的执行结果
-- 如果全部成功
COMMIT;
-- 如果任何一步失败
-- ROLLBACK;
7.5 事务的ACID特性
聊到事务,就绕不开ACID这四个字母。我尽量用大白话解释:
| 特性 | 含义 | 举个栗子 |
|---|---|---|
| 原子性(Atomicity) | 事务里的操作要么全做,要么全不做 | 转账:扣钱和加钱必须同时完成 |
| 一致性(Consistency) | 事务前后,数据必须符合所有规则 | 账户余额不能为负数 |
| 隔离性(Isolation) | 多个事务互不干扰 | 你转账时,别人查到的余额是中间状态还是最终状态? |
| 持久性(Durability) | 事务提交后,数据不会丢失 | 哪怕断电了,已提交的数据也还在 |
嗯,这里要注意:SQLite默认是自动提交模式,每条语句独立成一个事务。如果你想手动控制,就必须显式地写BEGIN和COMMIT。
7.6 避坑指南:我踩过的那些雷
最后,分享几个我亲身踩过的坑,希望能帮你少走弯路:
- 忘记WHERE条件:我曾经在调试时写了一条UPDATE,忘了加WHERE,结果整张表的价格全变成了同一个值。还好有备份,不然就凉了。
- 事务没提交:有一次我执行了BEGIN,然后做了一堆更新,结果忘了COMMIT,直接关闭了程序。第二天发现数据没变,白忙活一场。
- DELETE后没检查影响行数:我习惯在DELETE之后调用sqlite3_changes()检查实际删了多少行。如果结果是0,说明条件可能写错了,或者数据根本不存在。
- 嵌套事务的陷阱:SQLite支持savepoint,但嵌套太深容易搞混逻辑。我的建议是:尽量保持事务扁平化,别搞太复杂。
公众号:蓝海资料掘金营,微信deep3321