为什么SQL关联查询执行计划选择了全表扫描? 问题的根源,往往不是你的SQL写得有问题,而是数据库优化器“觉得”走索引不划算。这个“觉得”背后,依赖的是一套可能已经过时的统计信息。无论是MySQL、PostgreSQL还是Oracle,优化器都需要依据表的行数、列的数据分布(比如基数cardinal

问题的根源,往往不是你的SQL写得有问题,而是数据库优化器“觉得”走索引不划算。这个“觉得”背后,依赖的是一套可能已经过时的统计信息。无论是MySQL、PostgreSQL还是Oracle,优化器都需要依据表的行数、列的数据分布(比如基数cardinality)、索引页深度等数据来估算不同执行路径的成本。一旦这些统计信息没有及时更新,优化器就可能做出错误的判断。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
一个典型的表现就是:明明关联字段上创建了索引,但EXPLAIN的结果却显示type: ALL(MySQL)或Seq Scan(PostgreSQL)。更明显的信号是,rows列的估算值与实际值严重不符,比如估算扫描100行,实际却有50万行。
ANALYZE TABLE t1会重新采样数据,并更新information_schema.STATISTICS和mysql.innodb_table_stats中的信息。ANALYZE t1会更新pg_statistic系统表。需要注意的是,它的自动分析通常只在表的数据修改量超过一定阈值时才触发,大表可能长期得不到更新。DBMS_STATS.GATHER_TABLE_STATS过程。建议将ESTIMATE_PERCENT参数设为DBMS_STATS.AUTO_SAMPLE_SIZE,以获得更可靠的统计。ANALYZE?统计信息的更新并非实时同步。尤其是在数据发生剧烈变动后,旧的统计信息会立刻失效,导致执行计划劣化。以下几种场景几乎必然需要手动更新统计信息:
TRUNCATE或大批量DELETE操作(例如删除了表中80%的数据),之后没有跟进ANALYZE。LOAD DATA INFILE或COPY命令导入了百万级别以上的新数据。status = 'pending'的记录,但统计信息里还认为该列的值是均匀分布的)。JOIN 字段有索引却未被选用:检查三件事遇到这种情况,先别急着删除索引或重写SQL。不妨按顺序排查以下三点,确认是否是过时的统计信息误导了优化器:
SELECT COUNT(*) FROM t1 WHERE join_col = 'x',将结果与EXPLAIN输出中的rows估算值对比。如果两者相差10倍以上,那么统计信息不准的可能性就非常大。SELECT COUNT(DISTINCT join_col) / COUNT(*) FROM t1。如果结果低于0.01(意味着超过99%的值是重复的),那么即使有索引,优化器也很可能选择放弃使用它——这是一个基于成本的合理判断,并非系统bug。WHERE t1.a = t2.b这样的关联条件,需要确保t1表上有INDEX(a),t2表上有INDEX(b)。缺少其中任何一个,另一张表就可能被迫进行全表扫描来完成关联。ANALYZE 的风险与折中对全表执行ANALYZE在大表上可能带来风险:例如在部分版本中会锁表(MySQL 5.7+多数情况不锁,但可能加读锁),并消耗大量I/O资源。因此,绝对要避免在业务高峰期执行。更稳妥的做法是:
ANALYZE TABLE t1 UPDATE HISTOGRAM ON join_col WITH 16 BUCKETS这样的命令,只对关键列生成直方图统计,速度比全量分析快5到10倍。default_statistics_target参数的值(例如从默认的100设为200),以提升关键列的统计信息精度,再配合定期的ANALYZE任务。ANALYZE(MySQL从库默认关闭统计信息收集,PostgreSQL从库禁止写入操作)。所有更新统计信息的操作都应在主库完成。最后,还有一个最容易被忽略的盲点:许多ORM框架(例如Django的bulk_create)或ETL工具在批量插入数据后,并不会自动触发统计信息的更新。这不能指望开发人员每次都记得手动补上,而必须在运维侧建立配套的、定期的ANALYZE任务来保障。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述