数据库有哪几种(别再用代码循环查数据库了!这几种SQL写法让你优雅又高效)

数据库有哪几种(别再用代码循环查数据库了!这几种SQL写法让你优雅又高效)
别再用代码循环查数据库了!这几种SQL写法让你优雅又高效

作为一个每天和SQL打交道的资深开发,我经常在代码审查中看到这样的场景:业务同学在Java/Python里写for循环,一条一条去查数据库

最夸张的一次,我看到一段代码要更新1000条数据,居然循环了1000次去执行UPDATE。这不是在写业务,这是在给数据库“上刑”。

今天,我就站在DBA的角度,给你几种替代方案,把几十次的循环调用,压缩成一次SQL执行

一、为什么说代码循环查数据库是“反模式”?

我们先看一段“经典错误”代码:

// 反例:千万别这么写!for (Long userId : userIdList) {    String sql = "SELECT * FROM user WHERE id = " + userId;    // 执行一次查询...    // 再执行一次UPDATE...}

这种写法会引发三大灾难:

  1. 网络IO爆炸:循环N次,网络往返就是N次
  2. 数据库解析压力:同样的SQL结构被硬解析N次
  3. 事务拉锯战:N个小事务交替提交,性能急剧下降

下面这几种替代方案,是我在生产环境中验证过的“降维打击”方案。

二、方案一:用JOIN/IN代替循环查询(最常用)

场景:根据一批用户ID查询详细信息

错误做法

// 循环100次查询for (String id : idList) {    User user = userMapper.selectById(id);}

正确做法

-- 一次性查完SELECT * FROM user WHERE id IN (1001, 1002, 1003, ...);

MyBatis中配合标签即可动态拼接 。

三、方案二:用SQL批量UPDATE代替循环更新(效果最明显)

场景:给一批VIP用户打标签

错误做法

// 1000次循环更新for (String userId : vipList) {    jdbcTemplate.update("UPDATE user SET tag = 'VIP' WHERE id = ?", userId);}

正确做法

数据库有哪几种(别再用代码循环查数据库了!这几种SQL写法让你优雅又高效)

-- 一次性更新UPDATE user SET tag = 'VIP' WHERE id IN (1001, 1002, 1003, ...);

如果是复杂的条件判断,可以使用CASE WHEN语句:

UPDATE user SET tag = CASE id    WHEN 1001 THEN '黄金VIP'    WHEN 1002 THEN '白银VIP'    ELSE tagENDWHERE id IN (1001, 1002);

实测:10万条更新,从48秒降到了0.8秒 。

四、方案三:用存储过程封装批量逻辑(最高效)

有些复杂的业务逻辑,必须逐行判断。这时候千万别在Java里循环,把逻辑下推到数据库

以PostgreSQL为例,写一个存储过程处理批量日志打标:

CREATE OR REPLACE PROCEDURE batch_update_tags(    tag_name TEXT,     user_ids INT[]) LANGUAGE plpgsqlAS $$DECLARE    uid INT;BEGIN    FOREACH uid IN ARRAY user_ids LOOP        UPDATE user_log SET tag = tag_name WHERE user_id = uid;    END LOOP;END;$$;-- Java端只需要调用一次CALL batch_update_tags('大促用户', ARRAY[1001,1002,1003]);

Java调用代码:

CallableStatement stmt = conn.prepareCall("{call batch_update_tags(?, ?)}");stmt.setString(1, "大促用户");Array idArray = conn.createArrayOf("INTEGER", new Object[]{1001,1002,1003});stmt.setArray(2, idArray);stmt.execute(); // 一次调用,数据库内部循环

优势:网络开销从N次降到1次 。

五、方案四:分批处理+临时表(解决大事务锁表)

场景:删除/更新千万级数据

直接用一条DELETE删1000万行,会把整个表锁死,导致业务线崩盘。这时候要用分批+临时表

-- Step 1: 创建临时表,存放待处理的主键CREATE TEMPORARY TABLE tmp_ids (    id INT PRIMARY KEY) ENGINE=Memory;-- Step 2: 筛选待处理数据(只插ID)INSERT INTO tmp_ids SELECT id FROM orders WHERE create_time < '2023-01-01' LIMIT 100000;  -- 一次只处理10万-- Step 3: 分批更新SET @batch_size = 1000;REPEAT    UPDATE orders o    JOIN (SELECT id FROM tmp_ids LIMIT @batch_size) tmp    ON o.id = tmp.id    SET o.status = 'ARCHIVED';        DELETE FROM tmp_ids     WHERE id IN (SELECT id FROM tmp_ids LIMIT @batch_size);        COMMIT;  -- 每批提交,释放锁    DO SLEEP(0.01); -- 给数据库“喘口气”UNTIL ROW_COUNT() = 0 END REPEAT;

效果对比

方式

500万行耗时

锁表时间

业务影响

单条DELETE

120分钟

持续120分钟

业务瘫痪

分批处理

23分钟

<0.1秒/批

几乎无感

六、总结:什么时候该用什么?

场景

推荐方案

性能提升

查一批数据

JOIN / IN

提升N倍

简单批量更新

CASE WHEN

10~50倍

复杂逐行逻辑

存储过程

网络开销降为1/N

千万级大表操作

分批+临时表

避免锁表,保障可用性

最后送大家一句话:SQL是集合语言,不是过程语言。用集合思维写SQL,你会发现性能问题解决了一大半。

如果你还在代码里写循环查数据库,不妨试试上面的方法。优雅的代码,从来不需要那么多for循环



文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有

最新文章

热门文章

本栏目文章