程序员老鬼

北上广深程序员,在北京年 20w,回郑州要降薪还不到15w。笑哭,回老家, 这么难嘛?到底是谁在和我竞争啊!

刚刷到个吐槽帖,给我看沉默了。

说北上广深的程序员,在北京一年20w,结果想回郑州,薪资直接往下砍,别说20了,15w都费劲。笑哭,这回老家也不是回,是重新投胎啊。

Image

最扎心的是,你以为自己从一线回来,多少算个“降维打击”。结果一看岗位,要求没少,工资少一截,面试还挺挑。你会的他们都要,你不会的他们也写上,最后给的价格像在招实习生加强版。

然后评论区更真实:不是没人要你,是大家都想回去。大厂出来的、小厂熬过的、远程干不动的、家里催结婚买房的,全挤到那几个坑里。

所以问题来了,到底是谁在和我竞争?

面试题:批处理查询

接口压测时最难看的不是 CPU 飙高,是日志里一排这样的东西:

query user profile cost=18432ms, ids=10000
SQLSyntaxErrorException: too many parameters

这类问题我一般不先看业务逻辑。先看查询是不是一口气把所有 id 塞进去了。

批处理查询这题,表面是算法题,实际线上经常撞到数据库、RPC、ES、缓存这些东西的限制。一次查太少,网络来回耗死;一次查太多,SQL 直接炸,或者把数据库拖慢。

笨写法大概长这样:

List<UserInfo> users = userMapper.selectByIds(userIds);

看着干净,风险全藏在里面。

如果 userIds 只有几十个,没事。 如果是后台导入、定时补偿、报表回刷,一来就是几千上万,这行代码就不太可信了。

我一般会把它拆成小批次,每批 300 到 1000,看库的情况调,不写死在业务方法里。

publicclassBatchUserQuery{

privatefinal UserMapper userMapper;

publicBatchUserQuery(UserMapper userMapper){
this.userMapper = userMapper;
    }

public List<UserInfo> queryUsers(List<Long> rawIds, int batchSize){
if (rawIds == null || rawIds.isEmpty()) {
return Collections.emptyList();
        }

        List<Long> ids = rawIds.stream()
                .filter(Objects::nonNull)
                .distinct()
                .toList();

        Map<Long, UserInfo> userMap = new HashMap<>(ids.size() * 2);

for (int left = 0; left < ids.size(); left += batchSize) {
int right = Math.min(left + batchSize, ids.size());
            List<Long> part = ids.subList(left, right);

long start = System.currentTimeMillis();
            List<UserInfo> rows = userMapper.selectAliveUsers(part);
long cost = System.currentTimeMillis() - start;

if (cost > 800) {
                System.out.println("slow batch query, size=" + part.size() + ", cost=" + cost);
            }

for (UserInfo row : rows) {
                userMap.put(row.getUserId(), row);
            }
        }

        List<UserInfo> result = new ArrayList<>(rawIds.size());
for (Long id : rawIds) {
            UserInfo user = userMap.get(id);
if (user != null) {
                result.add(user);
            }
        }
return result;
    }
}

SQL 也别写得太飘:

select user_id, name, level
from user_info
where deleted = 0
and user_id in (...)

这里有几个细节容易被忽略。

第一,先去重。 同一个 id 查十次,数据库不会因为你诚恳就少干活。

第二,返回结果最好按原始 id 顺序组回去。 很多批处理后面还要和 Excel 行号、任务明细、消息列表对应,顺序一乱,排查起来很烦。

第三,别把 batchSize 写成 10000。 那不叫批处理,那叫换个姿势硬怼数据库。

第四,慢批次要打日志。 不是所有慢查询都会被你第一时间抓到,有时候就是某一批 id 命中了坏数据、冷数据、或者执行计划抽风。

我更喜欢这样的日志:

slow batch query, size=500, cost=1260

比一句“查询失败”有用多了。

这题的关键不在切数组,而在控制边界。 一次查多少,失败怎么重试,结果顺序要不要保留,空数据怎么处理,这些才是线上代码和刷题代码拉开差距的地方。

批处理查询写好以后,系统不会突然变快得离谱,但至少不会因为一批 2 万个 id,把数据库打得半天缓不过来。