设计合理的网站网页归档优秀网页设计图
设计合理的网站网页归档,优秀网页设计图,做百度收录比较好的网站,模板免费下载网址[TOC](count(*) 性能优化指南#xff1a;为什么慢#xff1f;怎么办#xff1f;)#x1f33a;The Begin#x1f33a;点点关注#xff0c;收藏不迷路#x1f33a;引言
在数据库开发中#xff0c;count(*) 是最常见的统计操作之一。然而#xff0c;很多开发者都遇到过这…[TOC](count(*) 性能优化指南为什么慢怎么办)The Begin点点关注收藏不迷路引言在数据库开发中count(*)是最常见的统计操作之一。然而很多开发者都遇到过这样的困扰表里只有几万条数据count(*)却要几百毫秒为什么 MyISAM 的 count(*) 飞快InnoDB 却这么慢有没有办法让 count(*) 变快本文将深入剖析count(*)的实现原理并给出几种实用的优化方案。1. count(*) 的实现差异1.1 MyISAM vs InnoDB1.2 为什么 InnoDB 不存储总行数-- 示例MVCC导致同一时刻不同事务看到不同行数-- 会话A事务BEGIN;SELECTCOUNT(*)FROMt;-- 返回 10000-- 会话B事务INSERTINTOtVALUES(10001);SELECTCOUNT(*)FROMt;-- 返回 10001-- 会话C自动提交INSERTINTOtVALUES(10002);SELECTCOUNT(*)FROMt;-- 返回 10002同一时刻三个查询看到10000行看到10001行看到10002行会话A结果不同会话B会话C核心原因InnoDB 的可重复读隔离级别下不同事务看到的数据行数可能不同因此无法预先存储一个固定的总行数。2. InnoDB 的 count(*) 优化虽然 InnoDB 必须逐行判断但还是做了优化2.1 选择最小的索引树-- 表结构CREATETABLEt(idINTPRIMARYKEY,nameVARCHAR(50),ageINT,KEYidx_age(age),KEYidx_name(name))ENGINEInnoDB;索引树大小比较最大中等最小选择最小索引主键索引包含所有字段优化器选择索引idx_age只含age索引idx_name只含nameidx_name原理count(*)只需要统计行数不需要读取具体数据因此优化器会选择最小的索引树来遍历减少IO开销。2.2 show table status 为什么不准-- 查看表状态SHOWTABLESTATUSLIKEt\G***************************1.row***************************Name: tEngine:InnoDBVersion:10Row_format: DynamicRows:10023-- 这个值是估算的Avg_row_length:78Data_length:782336注意TABLE_ROWS是从采样估算得来的可能误差很大不能用于精确计数。3. 优化方案自己计数如果需要频繁获取精确的总行数可以考虑以下三种方案。3.1 方案一使用缓存Redis业务逻辑插入数据Redis计数1删除数据Redis计数-1查询总数从Redis读取优点速度快适合高频查询缺点可能存在数据不一致两个典型的不一致场景-- 场景1Redis已更新但事务未提交-- 时间线-- T1: 插入数据事务未提交-- T2: Redis计数1-- T3: 查询Redis拿到新计数-- T4: 查询数据库事务未提交看不到新数据-- 结果计数多了但数据没查到-- 场景2Redis未更新但数据已提交-- 时间线-- T1: 插入数据并提交-- T2: 查询数据库看到新数据-- T3: Redis计数1延迟了-- 结果计数少了但数据查到了3.2 方案二数据库计数表推荐-- 创建计数表CREATETABLEcounter(table_nameVARCHAR(50)PRIMARYKEY,row_countINTUNSIGNEDNOTNULL)ENGINEInnoDB;-- 初始化INSERTINTOcounterVALUES(t,0);利用事务保证一致性-- 插入数据时更新计数BEGIN;INSERTINTOtVALUES(10001,张三,25);UPDATEcounterSETrow_countrow_count1WHEREtable_namet;COMMIT;-- 查询总数SELECTrow_countFROMcounterWHEREtable_namet;并发查询未提交已提交事务B查询事务A是否提交?看到旧计数看到新计数事务保证一致性BEGIN插入数据更新计数表COMMIT优点✅ 利用事务ACID特性保证精确一致✅ 崩溃恢复安全✅ 不受MVCC影响3.3 方案三定期汇总表适用于大数据的分析场景-- 创建汇总表CREATETABLEdaily_summary(stat_dateDATEPRIMARYKEY,table_nameVARCHAR(50),row_countINT);-- 每天凌晨统计INSERTINTOdaily_summarySELECTCURDATE(),t,COUNT(*)FROMt;-- 查询昨天的总数SELECTrow_countFROMdaily_summaryWHEREstat_dateCURDATE()-INTERVAL1DAY;4. 不同 count 用法的性能对比4.1 四种写法的区别-- 1. count(*)SELECTCOUNT(*)FROMt;-- 2. count(主键id)SELECTCOUNT(id)FROMt;-- 3. count(1)SELECTCOUNT(1)FROMt;-- 4. count(字段)SELECTCOUNT(name)FROMt;-- 注意不统计NULL4.2 执行流程对比┌─────────────────────────────────────────────────────────────────────────────┐ │ 四种count()写法的执行流程对比 │ ├───────────────┬───────────────────┬───────────────────┬─────────────────────┤ │count(主键id)│count(1)│count(字段)│count(*)│ ├───────────────┼───────────────────┼───────────────────┼─────────────────────┤ │ │ │ │ │ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ │ │InnoDB │ │ │InnoDB │ │ │InnoDB │ │ │InnoDB │ │ │ │遍历索引 │ │ │遍历索引 │ │ │遍历索引 │ │ │遍历索引 │ │ │ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ ▼ │ ▼ │ ▼ │ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ │ │ 取出id │ │ │ 不取值 │ │ │取出字段 │ │ │ 不取值 │ │ │ │ 值 │ │ │ │ │ │ 值 │ │ │ │ │ │ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ ▼ │ ▼ │ ▼ │ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ │ │Server层 │ │ │Server层 │ │ │Server层 │ │ │Server层 │ │ │ │判断非空 │ │ │ 放数字1│ │ │判断NULL│ │ │直接计数 │ │ │ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │ │ │ │ │ │ ┌──┴──┐ │ │ │ │ ▼ │ ▼ │ ▼ ▼ │ ▼ │ │ ┌─────────┐ │ ┌─────────┐ │ 累加 跳过 │ ┌─────────┐ │ │ │ 累加 │ │ │ 累加 │ │(非NULL)(NULL)│ │ 累加 │ │ │ └─────────┘ │ └─────────┘ │ │ └─────────┘ │ │ │ │ │ │ ├───────────────┼───────────────────┼───────────────────┼─────────────────────┤ │ 需要取值 │ 无需取值 │ 需要取值 │ 无需取值 │ │ 需要判空 │ 无需判空 │ 需要判NULL│ 无需判空 │ │ 性能★★★ │ 性能★★★★ │ 性能★★ │ 性能★★★★★ │ └───────────────┴───────────────────┴───────────────────┴─────────────────────┘4.3 性能排序写法执行过程性能说明count(字段)取值 判NULL⭐⭐最慢特别是字段允许NULL时count(主键id)取值 累加⭐⭐⭐需要读取id值count(1)放数字 累加⭐⭐⭐⭐不取值但Server层有操作count(*)直接累加⭐⭐⭐⭐优化器特殊处理最快结论count(*)≈count(1)count(主键id)count(字段)5. 实际案例对比5.1 测试数据-- 创建测试表100万行CREATETABLEtest_count(idINTPRIMARYKEYAUTO_INCREMENT,nameVARCHAR(50),ageINT,INDEXidx_age(age))ENGINEInnoDB;-- 插入100万条数据-- ...省略插入语句5.2 性能测试结果查询语句执行时间说明SELECT COUNT(*) FROM test_count;0.32秒优化器选择最小索引SELECT COUNT(id) FROM test_count;0.35秒读取主键索引SELECT COUNT(1) FROM test_count;0.31秒与count(*)相当SELECT COUNT(name) FROM test_count;0.48秒需要判NULLSHOW TABLE STATUS;0.01秒不精确仅供参考6. 生产环境建议6.1 不同场景的选型业务场景推荐方案理由前台实时计数如订单总数计数表事务中维护精确、一致大屏展示可接受延迟缓存 异步更新性能好偶尔不一致可接受后台报表T1每日汇总表避免实时计算简单查询小表直接count(*)简单直接6.2 代码示例计数表实现ServicepublicclassOrderService{AutowiredprivateOrderMapperorderMapper;AutowiredprivateCounterMappercounterMapper;TransactionalpublicvoidcreateOrder(Orderorder){// 1. 插入订单orderMapper.insert(order);// 2. 更新计数counterMapper.increment(orders);}publiclonggetOrderCount(){// 直接从计数表查询returncounterMapper.getCount(orders);}}-- CounterMapper.xmlupdateidincrementUPDATEcounterSETrow_countrow_count1WHEREtable_name#{tableName}/updateselectidgetCountresultTypelongSELECTrow_countFROMcounterWHEREtable_name#{tableName}/select总结知识点核心内容MyISAM的count存储总行数直接返回InnoDB的count逐行判断可见性精确但慢优化器行为选择最小索引遍历count(*)≈ count(1)最快count(字段)最慢尤其字段允许NULL时计数表方案利用事务保证精确一致缓存方案速度快但可能不一致最佳实践小表10万直接count(*)大表且需要精确用计数表事务维护大表且可接受延迟用缓存或汇总表记住没有银弹根据业务场景选择合适的方案。The End点点关注收藏不迷路