文章内容
2026/1/21 20:07:51,作 者: 黄兵
一条经典 SQL 查询语句
这是一条 SQL 查询语句:
INSERT INTO cloud_product_account_balance (user_id, balance, time_updated, create_time) VALUES (%s, %s, UTC_TIMESTAMP(), UTC_TIMESTAMP()) ON DUPLICATE KEY UPDATE balance = balance + VALUES(balance), time_updated = UTC_TIMESTAMP()
这条 SQL 语句非常经典且高效,通常被称为 "Upsert"(Update + Insert)。
它的核心作用是:“如果记录不存在就插入,如果记录存在就更新”,而且这一切是在一条 SQL 语句中原子完成的。
让我们拆解一下这条语句的逻辑:
1. 核心逻辑拆解
这条语句分为三部分来看:
第一部分:尝试插入
INSERT INTO cloud_product_account_balance (user_id, balance, ...) VALUES (%s, %s, ...)
动作:首先,数据库尝试把它当做一个普通的插入操作。
假设:我们要插入
user_id=101,balance=50.00。
第二部分:捕获冲突
ON DUPLICATE KEY
触发条件:在你的
cloud_product_account_balance表模型中,user_id字段被定义为unique=True(唯一索引)。逻辑:
如果数据库里没有
user_id=101的记录,第一部分的插入就成功了,流程结束。如果数据库里已经有
user_id=101的记录,插入会失败(报 Duplicate entry 错误)。此时,数据库不会报错,而是触发后面的UPDATE操作。
第三部分:执行更新
UPDATE
balance = balance + VALUES(balance),
time_updated = UTC_TIMESTAMP()这是最精彩的地方,只有在发生冲突(即用户已存在)时才会执行:
balance(等号右边):指数据库里现有的余额(比如用户之前有 100 元)。VALUES(balance):指你刚才试图插入、但没插进去的那个值(本次退款的 50 元)。结果:
新余额 = 旧余额(100) + 本次退款(50)= 150 元。
2. 举个生动的例子
假设我们要给用户 ID: 888 退款 $10。
场景 A:这是一个新用户(或者他以前从来没有余额记录)
执行: 试图插入
(user_id=888, balance=10)。检查: 表里没有 888。
结果: 插入成功。现在表里有一行:
{user_id: 888, balance: 10}。
场景 B:这是一个老用户(表里已经有他的记录,余额 $200)
执行: 试图插入
(user_id=888, balance=10)。检查: 表里已经有 888 了!触发
ON DUPLICATE KEY UPDATE。计算:
balance(旧值) = 200VALUES(balance)(新来的值) = 10balance = 200 + 10
结果: 更新成功。现在表里的记录变成:
{user_id: 888, balance: 210}。
3. 为什么要在这里用它?
在你的代码中,使用这个写法有三个巨大的好处:
代码简洁 (Simplicity):
不用写: 先
SELECT查一下有没有人 ->if有人则UPDATE->else没人则INSERT。一行 SQL 搞定所有情况。
并发安全 (Concurrency Safety):
如果是“先查后写”的两步操作,在高并发下可能会出 Bug(比如两个线程同时查到“没记录”,然后都试图插入,导致其中一个报错)。
这条 SQL 是原子性的,MySQL 会自动加锁,保证资金计算绝对安全。
数据完整性 (Robustness):
它完美处理了“用户第一次产生退款”这种边界情况,不需要你手动去初始化用户的余额表。
总结
这句话的意思是:“给这个用户存入这笔钱。如果他是新用户,就直接建档存入;如果他是老用户,就在他原有余额的基础上加上这笔钱。”
其它相关推荐:
5、商品收藏相关问题
评论列表