记录一次MySQL数据库批量更新的麻烦事
论坛发帖,需要记录浏览次数,为了减轻数据库压力,采用Redis+延时任务的模式,批量更新到数据库,下图中views字段为浏览次数

大致思路:
1. 访问帖子详情时 把访问次数存放到Redis

private long getViewsCount(int pid) {
String key = Const.FORUM_POST_VIEW_COUNT;
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
long views = this.getById(pid).getViews();
redisTemplate.opsForHash().put(key,String.valueOf(pid),String.valueOf(views));
// redisTemplate.opsForValue().set(key, String.valueOf(views));
}
Long increment = redisTemplate.opsForHash().increment(key, String.valueOf(pid), 1);
this.saveViewsSchedule();
return increment;
}
2. 执行定时任务saveViewsSchedule
private final Map<String, Boolean> state = new HashMap<>();
ScheduledExecutorService viewService = Executors.newScheduledThreadPool(1);
//为了测试 把延迟时间缩短为10s
private void saveViewsSchedule() {
if (!state.getOrDefault("views", false)) {
state.put("views", true);
log.info("保存Views定时任务执行!");
viewService.schedule(() -> {
try {
this.saveViews();
} catch (Exception e) {
e.printStackTrace();
}
state.put("views", false);
}, 10, TimeUnit.SECONDS);
}
}
3. 一定时间后执行saveViews
private void saveViews() {
// map中 key:id , value 更新的值
Map<Integer,Long> map = new HashMap<>();
redisTemplate.opsForHash().entries(Const.FORUM_POST_VIEW_COUNT).forEach((k,v)->{
map.put(Integer.valueOf(k.toString()), Long.valueOf(v.toString()) );
redisTemplate.opsForHash().delete(Const.FORUM_POST_VIEW_COUNT,k);
});
Long updated = postMapper.updateViews(map);
log.info("saveViews 更新了 {} 条记录",updated);
}
4. 执行postMapper.updateViews(map) 关键的来了!
经过搜索,一般批量更新有两种方式:
1. 一句一句执行 使用Mybatis foreach 循环执行 比如:
UPDATE db_post_copy1 set views = 3 where id = 14
UPDATE db_post_copy1 set views = 3 where id = 14
......
2. 使用下面这种批量更新语法
UPDATE db_post_copy1
SET views = CASE
WHEN id = 14 THEN 3
WHEN id = 15 THEN 5
WHEN id = 16 THEN 6
ELSE views
END
WHERE id IN (14,15,16)
理论上是第二种方式快的多
于是就开始研究 Mybatis 动态拼接 (最终失败了,总是报错map为空,错误代码就不贴了)
厉害的大佬可以去看 https://www.cnblogs.com/eternityz/p/12284760.html
换个思路:
Mybatis中拼接SQL很费力气,不如直接在Java中拼接字符串!
(代码AI生成 自己拼接得到什么时候)
package com.example.backend.utils;
import java.util.Map;
public class SqlProvider {
public String updateViews(Map<Integer, Long> map) {
StringBuilder sql = new StringBuilder("UPDATE db_post SET views = CASE id ");
for (Map.Entry<Integer, Long> entry : map.entrySet()) {
sql.append("WHEN ").append(entry.getKey()).append(" THEN ").append(entry.getValue()).append(" ");
}
sql.append("ELSE views END WHERE id IN (");
for (Integer id : map.keySet()) {
sql.append(id).append(",");
}
// 删除最后一个逗号
sql.deleteCharAt(sql.length() - 1);
sql.append(")");
return sql.toString();
}
}
postmapper:
(第一次知道还能这么写。。。)
@UpdateProvider(type = SqlProvider.class, method = "updateViews")
Long updateViews(Map<Integer, Long> map);
测试成功。










Interesting analysis! Seeing more platforms like JILI PG cater specifically to the Philippine market is smart. Easy access via the jili pg download & app is key for mobile gamers, and a localized approach really boosts engagement. Great points!
Sprunki Game truly elevates the Incredibox experience with its rich sounds and creative depth. It’s amazing how they blend music and visuals so seamlessly. Check it out at Sprunki Game!