MySQL查询运行时更好,即使它的执行计划不好

MySQL查询运行时更好,即使它的执行计划不好,mysql,sql,indexing,query-optimization,Mysql,Sql,Indexing,Query Optimization,我试图优化这个MySQL查询,但在理解执行计划方面经验不足,我很难理解执行计划 我的问题是:你能帮助我理解为什么新查询的查询执行计划比原始查询的查询执行计划差,即使新查询在生产中表现更好 重现此案例所需的SQL为 最后还保留了相关的表定义(表bill\u范围引用使用外键bill\u id的bill) 原始查询需要10秒才能在PROD中完成 select * from bill_range where (4050 between low and high ) order by bill_id

我试图优化这个MySQL查询,但在理解执行计划方面经验不足,我很难理解执行计划

我的问题是:你能帮助我理解为什么新查询的查询执行计划比原始查询的查询执行计划差,即使新查询在生产中表现更好

重现此案例所需的SQL为
最后还保留了相关的表定义(表bill\u范围引用使用外键bill\u id的bill)

原始查询需要10秒才能在PROD中完成

select * 
from bill_range 
where (4050 between low and high ) 
order by bill_id limit 1;
select * 
from bill_range 
use index ( bill_range_low_high_index) 
where (4050 between low and high ) 
order by bill_id limit 1;
新查询(我强制/建议使用索引)需要5秒钟才能在PROD中完成

select * 
from bill_range 
where (4050 between low and high ) 
order by bill_id limit 1;
select * 
from bill_range 
use index ( bill_range_low_high_index) 
where (4050 between low and high ) 
order by bill_id limit 1;
但执行计划显示原始查询更好(这部分我的理解似乎是错误的)

原始查询

新查询

  • 原始查询的“类型”列在新查询时建议索引 全说
  • 列“Key”是用于 原始查询,新查询为空
  • 原始查询的“行”列为1,而新查询的列为9
  • 所以,考虑到所有这些信息,这不意味着新查询实际上比原始查询更糟糕吗。 如果这是真的,为什么新查询的性能更好?还是我看错了执行计划

    表格定义

    CREATE TABLE bill_range (
        id int(11) NOT NULL AUTO_INCREMENT,
        low varchar(255) NOT NULL,
        high varchar(255) NOT NULL,
        PRIMARY KEY (id),
        bill_id int(11) NOT NULL,
        FOREIGN KEY (bill_id) REFERENCES bill(id)
    );
    CREATE TABLE bill (
        id int(11) NOT NULL AUTO_INCREMENT,
        label varchar(10),
        PRIMARY KEY (id)
    );
    create index bill_range_low_high_index on bill_range( low, high);
    

    注意:我之所以提供2个表的定义,是因为原始查询决定使用基于外键的索引对账单表进行查询

    您的索引对您的查询不是很理想。如果可以的话,让我解释一下

    MySQL索引使用BTREE数据结构。它们在索引顺序访问模式下工作得很好(因此MySQL的第一个存储引擎名为MyISAM)。它支持跳转到索引中的特定位置,然后逐个元素遍历索引元素的查询。典型的例子是,索引位于
    col

     SELECT whatever FROM tbl WHERE col >= constant AND col <= constant2
    
    high
    列上的索引允许从
    high>=4050的第一个合格行开始进行范围扫描。然后,我们可以继续使其成为一个复合索引,包括
    bill\u id
    low

    在票据范围(高、票据id、低)上创建索引高、票据id、低;
    
    因为我们需要最低匹配的
    bill\u id
    ,所以我们接下来将其放入索引中,最后是
    low
    值。因此,查询计划器通过
    high
    随机访问第一行的索引,然后进行扫描,直到找到满足
    low
    标准的第一个索引项。然后就完成了:这就是期望的结果。它已经由bill\u id订购,因此可以停止<代码>订购依据
    来自索引。查询可以完全通过索引来满足——它是一个所谓的覆盖索引

    至于为什么您的两个查询执行得不同:在第一个查询中,查询计划员决定按
    账单id
    顺序扫描您的数据,以查找第一个匹配的
    /
    对。它可能决定,对结果集进行实际排序可能比按顺序扫描
    bill\u id
    s更昂贵。在我看来,您的第二个查询进行了表扫描。为什么更快,谁知道呢

    请注意,此索引也适用于您

    在票据范围上创建低票据id高的索引(低下降,票据id,高);
    
    在InnoDB中,表的PK
    id
    隐含在每个索引中,因此无需在复合索引中提及它

    而且,你仍然可以按照你第一次写的方式来写;查询计划器将计算出您想要的内容


    专业提示:避免
    选择*
    。。。
    *
    使得您更难对需要检索的列进行推理。

    索引的基数是多少?如果它的基数很差,那么使用索引也没有帮助。查询没有使用
    bill
    表,为什么我们需要它的定义?优化器并不总是正确的,并且您的基准可能不可复制。例如,当您在10秒时对第一个查询进行基准测试时,当时可能有其他进程在MySQL上运行。通常,您希望避免强制使用索引,因为MySQL比您更聪明(大多数情况下)。@Barmar在PROD中bill\u range\u low\u high\u索引的基数为200KNO NO!不要将数字(
    4500
    )与
    VARCHAR
    )进行比较!!解决这个问题,然后重新检查这里讨论的所有内容;它可能不再有效。我认为“重写”正是解析器所做的。做
    解释。。。;显示警告验证。关于索引的注释:
    INDEX(x DESC,y ASC)
    尊重MySQL 8.0中的
    DESC
    。它在所有其他版本中都被忽略。只有在执行按x描述、y描述的
    订单和按x描述、y描述的
    订单时,此注释才很重要。我无法想象它在
    中的重要性。注释/答案教会了我很多东西。几个值得注意的地方:(1)列
    low
    high
    属于varchar类型(如在我的产品中)。(2)
    显示警告
    got
    由于字段“low”的类型或排序规则转换,无法对索引“bill\u range\u low\u high\u index”使用范围访问
    。(3) 如果我将
    int
    用于
    low
    high
    ,新查询的执行计划会有所改进,但原始查询执行计划仍然更好,那么这种情况就会消失。(4) TODO:如果更改为
    int
    ,我无法在实际项目中测试运行时。将更新结果。(5) 解决方案中建议的索引未更改执行计划