记录一次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);
测试成功。
评论