“过早优化是万恶之源,但数据库优化除外” - 这次聚焦在数据库开发优化上,其中 大事务 是数据库性能的“头号杀手”,会重点展开,从识别、拆分、异步化几个角度切入,尤其要强调锁竞争和回滚风险。除了大事务,也包括一些其他优化点:索引设计、批处理、连接池调优、读写分离等等。
java @Transactional // 默认60秒超时 public void payCallback(PayMessage msg) { // 1. 更新订单状态(锁订单行) orderDao.updateStatus(msg.getOrderId(), PAID); // 2. 生成会计凭证(耗时统计操作) accountingService.createEntries(msg); // 平均耗时45秒 // 3. 发放用户权益(调用外部服务) benefitService.grantVoucher(msg.getUserId()); // 网络波动时超时 }
产生的影响: 订单表长期锁等待 → 支付服务线程池打满 → 整个支付链路瘫痪
1、业务解耦 - 异步化非核心操作
java @Transactional public void payCallback(PayMessage msg) { orderDao.updateStatus(msg.getOrderId(), PAID); // 仅保留核心事务 // 异步化下游操作 accountingAsyncService.createEntries(msg); benefitAsyncService.grantVoucher(msg.getUserId()); }
2、分而治之 - 拆分事务粒度
java public void handleLargeBatch() { List<Data> batch = queryLargeData(); for (int i = 0; i < batch.size(); i += 100) { // 每100条提交一次事务 processBatch(batch.subList(i, Math.min(i+100, batch.size()))); } } @Transactional(propagation = Propagation.REQUIRES_NEW) // 独立事务 public void processBatch(List<Data> batch) { ... }
3、 状态机管理 - 避免长事务锁
java -- 设计中间状态字段 UPDATE orders SET status = 'PROCESSING' -- 非终态不阻塞查询 WHERE status = 'NEW' AND id = 10086;
4、妥协策略 - 最终一致性补偿
text 支付回调 -> 本地事务: 更新订单状态 本地事务 -> 消息队列: 发送会计事件 消息队列 -> 会计服务: 消费事件(可能重试) 会计服务 -> 支付回调: 处理失败? 支付回调 -> 补偿服务: 触发补偿机制
sql -- 案例:1.2亿用户表查询缓慢 SELECT * FROM users WHERE YEAR(create_time) = 2023 -- 索引失效! AND status = 1;
左匹配原则
sql -- 重建联合索引(避免函数计算) ALTER TABLE users ADD INDEX idx_status_ctime(status, create_time); -- 正确查询姿势 SELECT * FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31' AND status = 1;
覆盖索引降IO
sql -- 避免回表查询 SELECT id, name FROM users WHERE phone = '13800138000'; -- 索引覆盖:INDEX(phone, name)
拒绝过度索引
sql -- 索引代价测试(插入10w数据) | 索引数量 | 插入耗时 | |---------|----------| | 0 | 1.2s | | 5 | 3.8s | | 10 | 8.5s |
定期索引体检
sql -- 使用诊断工具(MySQL示例) EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 10086; -- 清理无效索引 SELECT * FROM sys.schema_unused_indexes;
当下的计算机CPU处理速度还是很多的,而 IO 一般是个瓶颈,如:磁盘IO、网络IO。
有这么一个场景,查询 100 个人的账户余额?
有两个设计方案:
你觉得那种方案更好?答案不言而喻,肯定是方案二
数据库的写操作也是一样道理,为了提高性能,我们一般都是采用批量更新。
java //创建商品的controller @RequestMapping(value = "/create",method = {RequestMethod.POST}) @ResponseBody public CommonReturnType createItem(@RequestParam(name = "title")String title, @RequestParam(name = "description")String description, @RequestParam(name = "price")BigDecimal price, @RequestParam(name = "stock")Integer stock, @RequestParam(name = "imgUrl")String imgUrl) throws BusinessException { //封装service请求用来创建商品 ItemModel itemModel = new ItemModel(); itemModel.setTitle(title); itemModel.setDescription(description); itemModel.setPrice(price); itemModel.setStock(stock); itemModel.setImgUrl(imgUrl); ItemModel itemModelForReturn = null; for(int i = 1 ;i <=100;i ++){ itemModel.setId(null); itemModelForReturn = itemService.createItem(itemModel); } ItemVO itemVO = convertVOFromModel(itemModelForReturn); //rocketMQTemplate.convertAndSend("item-update-mq", itemModel.getId()); bloomFilter.add("item_" + itemModel.getId()); return CommonReturnType.create(itemVO); }
java @RequestMapping(value = "/getItems",method = {RequestMethod.GET}) @ResponseBody public CommonReturnType getItems(@RequestParam(name = "startId")Integer startId,@RequestParam(name = "endId")Integer endId){ log.info("批量获取数据开始"); List<ItemVO> itemVOs = new ArrayList<>(); Date startTime = new Date(); for(int i = startId;i <= endId; i++){ ItemModel itemModel = itemService.getItemById(i); ItemVO itemVO = convertVOFromModel(itemModel); itemVOs.add(itemVO); } Date endTime = new Date(); log.info("批量获取数据总共耗时:{}",startTime.getTime() - endTime.getTime()); return CommonReturnType.create(itemVOs); }
java @RequestMapping(value = "/getItemsByBath",method = {RequestMethod.GET}) @ResponseBody public CommonReturnType getItemsByBath(@RequestParam(name = "startId")Integer startId,@RequestParam(name = "endId")Integer endId){ log.info("批量获取数据开始"); List<ItemVO> itemVOs = new ArrayList<>(); Date startTime = new Date(); List<ItemModel> itemModels = itemService.getItemsByBath(startId,endId); for (ItemModel itemModel : itemModels) { ItemVO itemVO = convertVOFromModel(itemModel); itemVOs.add(itemVO); } Date endTime = new Date(); log.info("批量获取数据总共耗时:{}",endTime.getTime() - startTime.getTime()); return CommonReturnType.create(itemVOs); }
yaml # Spring Boot配置示例 spring: datasource: hikari: maximum-pool-size: 20 # = (核心数*2) + 磁盘数 minimum-idle: 5 connection-timeout: 3000 idle-timeout: 600000 max-lifetime: 1800000 connection-test-query: SELECT 1
java // 运行时热调整连接池 HikariPool pool = (HikariPool) dataSource.getHikariPoolMXBean(); pool.setMaximumPoolSize(30); // 应对流量高峰
检查最长事务:
sql SELECT * FROM information_schema.innodb_trx ORDER BY trx_started LIMIT 10;
对核心表执行 EXPLAIN 体检
事务分析:pt-kill
慢查询分析:Percona Toolkit
压力测试:sysbench
可视化监控:Prometheus + Grafana
本文作者:张豪
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!