尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

多表join如何优化

多表join如何优化 优化JOIN的核心不是加索引而是减少数据量和减少扫描次数。 原始需求查询最近30天的订单显示订单号、用户名、商品名、数量、总价。 表结构简化· 订单表 orders50万行含 user_id、create_time· 用户表 users10万行含 user_name· 商品表 products5万行含 product_name· 订单明细表 order_items200万行含 order_id、product_id、quantity、price 优化前典型慢SQLsqlSELECT o.order_id, u.user_name, p.product_name,oi.quantity, oi.priceFROM orders oJOIN users u ON o.user_id u.user_idJOIN order_items oi ON o.order_id oi.order_idJOIN products p ON oi.product_id p.product_idWHERE o.create_time NOW() - INTERVAL 30 DAYORDER BY o.create_time DESCLIMIT 100;慢在哪· 驱动表 orders 扫描30天数据假设20万行· 每行要 3次B树查找users、order_items、products· 200万行的 order_items 被大量随机读取· 全部JOIN完才排序取100条中间结果集巨大 优化过程4步第1步EXPLAIN看执行计划sqlEXPLAIN SELECT ...发现 orders 用 create_time 索引但 order_items 的 order_id 没索引——全表扫描200万行先给它建上。第2步被驱动表建索引sqlALTER TABLE order_items ADD INDEX idx_order_id (order_id);ALTER TABLE order_items ADD INDEX idx_product_id (product_id);建完后执行时间从 8秒 降到 2秒但还慢。第3步改写SQL——先缩小再关联把30天的订单ID先查出来只取需要的字段再JOIN其他表sqlSELECT o.order_id, u.user_name, p.product_name,oi.quantity, oi.priceFROM (SELECT order_id, user_id, create_timeFROM ordersWHERE create_time NOW() - INTERVAL 30 DAYORDER BY create_time DESCLIMIT 100 -- 只取前100条) oJOIN users u ON o.user_id u.user_idJOIN order_items oi ON o.order_id oi.order_idJOIN products p ON oi.product_id p.product_id;关键变化先LIMIT再JOIN驱动表从20万行变成100行后续JOIN只执行100次执行时间降到 0.05秒。第4步覆盖索引再加速sql-- orders表建覆盖索引ALTER TABLE orders ADD INDEX idx_time_id (create_time, order_id, user_id);子查询直接从索引取数据不回表再快一倍。--- 优化效果对比指标 优化前 优化后扫描行数 20万 100行JOIN次数 20万次 100次执行时间 8秒 0.03秒临时表 有 无 从这个案例记住3个原则1. 先缩小再关联用子查询提前过滤2. 被驱动表关联字段必须索引order_id、product_id3. LIMIT下推到子查询避免JOIN完再取前N条其它方案业务允许可以违反范式冗余字段空间换时间。适合用户信息、商品名称等低频变更字段应用层拆分sql高并发场景但要注意设置最大结果集限制使用缓存关联表数据全量/热点加载到缓存应用层查缓存代替JOIN。但要注意数据一致性问题宽表通过定时任务把JOIN结果提前算好存成一张宽表查询直接查宽表。适合报表类对实时性要求不高的场景搜索引擎多条件全文检索海量数据复杂筛选预算表提前算好countsum聚合结果。查的时候直接用方案使用场景看数据大小、看读写比例、看实时性要求。· 数据量小 → 索引JOIN· 读多写少 → 冗余缓存· 数据量大多维度查询 → ES/大宽表· 高并发核心链路 → 应用层拆分。方案缺陷冗余字段用户改名时要批量更新订单表千万级数据压力山大可用最终一致性解决允许短时显示旧名通过MQ异步修正Redis缓存缓存穿透/雪崩/击穿三大坑要防另外缓存与DB数据不一致窗口期需评估业务是否可接受比如允许5秒延迟大宽表宽表字段太多会导致行溢出MySQL单行约8KB上限建议用ES或列式存储ES实时性差一般秒级延迟且需要额外运维成本拆分sql对比JOIN方案· ✅ 优点网络开销小、代码简洁、数据库内部优化如Nested Loop优化· ❌ 缺点数据库成为性能瓶颈、无法水平扩展、慢查询会拖垮所有业务应用层并行方案· ✅ 优点故障隔离、可水平扩展、各表独立缓存命中率高、分库分表后依然能用· ❌ 缺点网络IO翻倍、应用内存有压力、需要处理数据一致性、代码复杂度上升实际生产的主流做法1. 90%的普通查询 → JOIN搞定配合索引和SQL优化因为简单、可维护2. 9%的高并发核心接口 → 应用层拆分并行把压力从DB转移到App3. 1%的复杂报表/搜索 → ES或大宽表彻底脱离关系型数据库加一道保险· 所有JOIN查询设置 超时熔断如2秒超时自动取消· 应用层并行查询设置 最大结果集限制如一次最多查1000条防止OOM总结优先用JOIN保持代码简单但当QPS上来后通过监控告警发现数据库连接池使用率超过70%我会立即切换到应用层拆分方案并配合缓存降低DB压力。
返回列表