【问题标题】:Why doesn't MySQL use the primary key on JOIN plus ORDER?MySQL为什么不使用JOIN加ORDER的主键?
【发布时间】:2012-11-01 14:35:17
【问题描述】:

这是给你的一个简洁的(显然是 MySQL):

# 设置东西 如果存在则删除数据库 index_test_gutza; 创建数据库 index_test_gutza; 使用 index_test_gutza; 创建表客户订单( id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, 发票 MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, 主键(id) ); 插入到 customer_order (身份证,发票) 价值观 (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); 创建表 customer_invoice ( id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, invoice_no MEDIUMINT UNSIGNED DEFAULT NULL, invoice_pdf LONGBLOB, 主键(id) ); 插入到 customer_invoice (id, invoice_no) 价值观 (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); # 好的,这是牛肉 解释 选择 co.id FROM customer_order AS co; 解释 选择 co.id FROM customer_order AS co 按 co.id 订购; 解释 选择 co.id,ci.invoice_no FROM customer_order AS co 左加入 customer_invoice AS ci ON ci.id=co.invoice; 解释 选择 co.id,ci.invoice_no FROM customer_order AS co 左加入 customer_invoice AS ci ON ci.id=co.invoice 按 co.id 订购;

底部有四个 EXPLAIN 语句。前两个结果完全符合您的预期:

+----+-------------+--------+-------+-------------- -+---------+---------+------+------+-----------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+--------+-------+-------------- -+---------+---------+------+------+-----------+ | 1 |简单 |合作 |索引 |空 |初级 | 3 |空 | 5 |使用索引 | +----+-------------+--------+-------+-------------- -+---------+---------+------+------+-----------+

第三个已经很有趣了——注意 customer_order 中的主键不再使用了:

+----+-------------+--------+--------+------------- --+---------+---------+-------------- --+--------+--------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+--------+--------+------------- --+---------+---------+-------------- --+--------+--------------+ | 1 |简单 |合作 |全部 |空 |空 |空 |空 | 5 | | | 1 |简单 |词 | eq_ref |初级 |初级 | 3 | index_test_gutza.co.invoice | 1 |使用索引 | +----+-------------+--------+--------+------------- --+---------+---------+-------------- --+--------+--------------+

然而,第四个是 zinger - 只需在主键上添加 ORDER BY 就会导致在 customer_order 上进行文件排序(这是意料之中的,因为上面已经被弄糊涂了) :

+----+-------------+--------+--------+------------- --+---------+---------+-------------- --+--------+----------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+--------+--------+------------- --+---------+---------+-------------- --+--------+----------------+ | 1 |简单 |合作 |全部 |空 |空 |空 |空 | 5 |使用文件排序 | | 1 |简单 |词 | eq_ref |初级 |初级 | 3 | index_test_gutza.co.invoice | 1 |使用索引 | +----+-------------+--------+--------+------------- --+---------+---------+-------------- --+--------+----------------+

文件排序!虽然我从不使用 customer_order 表中的主键进行订购,以及 customer_invoice 表中的主键用于 JOIN。那么,以所有好的和正确的名义,为什么它会突然切换到文件排序?!更重要的是,我该如何避免这种情况?作为记录,我很乐意接受一份书面回答,解释为什么无法避免这种情况(如果是这样的话)。

正如您现在可能怀疑的那样,这实际上是在生产中发生的,虽然这些表绝不是巨大的(只有数百条记录),但 invoice 表(包含 PDF 文件)上的文件排序正在杀死服务器当我运行与上述类似的查询时(我需要这些查询以了解哪些订单已开具发票,哪些未开具发票)。

在你问之前,我设计了数据库,我认为我可以安全地将 PDF 文件存储在该表中,因为我从不需要任何搜索查询——我总是有它的主键就在眼前!

更新(cmets 概要)

以下是以下 cmets 中建议的概要,因此您不必阅读所有内容:

  • *您应该在 customer_order.invoice* 上添加一个键——我实际上在生产中尝试过,它没有任何区别(因为它不应该)
  • 你应该使用USE INDEX——试过了,没用。我也试过FORCE INDEX——也没有结果(没有任何变化)
  • 您过度简化了用例,我们需要实际的生产查询——我可能在第一次迭代中剥离了太多,所以我更新了它(我刚刚在 @987654324 中添加了 , ci.invoice_no @ 用于最后几个查询)。作为记录,如果有人真的很好奇,这里是生产查询,原样(检索订单的最后一页):
选择 corder.id, corder.public_id, CONCAT(buyer.fname," ",buyer.lname) 作为买家名称, corder.status, 电汇付款, corder.reserved AS R, corder.tracking_id!="" 作为 A, corder.payment_received 作为 pay_date, invoice.invoice_no AS inv, invoice.receipt_no AS 收据, invoice.public AS pub_inv, proforma.proforma_no AS 教授, proforma.public AS pub_pf, 评分, corder.rating_cmets!="" AS got_comment 从 绳索 在buyer.id=corder.buyer 上左加入用户作为买家 LEFT JOIN invoice as invoice ON invoice.id=corder.invoice LEFT JOIN invoice as proforma ON proforma.id=corder.proforma 订购方式 编号 DESC 限制 400, 20;

上面的查询(同样,这正是我在生产中运行的)大约需要 14 秒才能运行。这是在生产环境中执行的简化查询,如上面的用例所示:

选择 corder.id, invoice.invoice_no 从 绳索 LEFT JOIN invoice ON invoice.id=corder.invoice 订购方式 corder.id DESC 限制 400, 20;

这个需要 13 秒才能运行。请注意,LIMIT 没有任何区别只要我们谈论的是结果的最后一页(我们是)。也就是说,当涉及到文件排序时,检索最后 12 个结果或全部 412 个结果之间绝对没有显着差异。

结论

ypercube 的答案不仅正确,而且不幸的是它似乎是唯一合法的答案。我试图进一步将条件与字段分开,因为 SELECT * FROM corder 子查询最终可能涉及大量数据,如果 corder 本身包含 LONGBLOB(并且从子查询中的主查询复制字段是不优雅的),但不幸的是它没有t 似乎工作:

选择 corder.id, corder.public_id, CONCAT(buyer.fname," ",buyer.lname) 作为买家名称, corder.status, 电汇付款, corder.reserved AS R, corder.tracking_id != "" AS A, corder.payment_received AS pay_date, invoice.invoice_no AS inv, invoice.receipt_no AS 收据, invoice.public AS pub_inv, proforma.proforma_no AS 教授, proforma.public AS pub_pf, 评分, corder.rating_cmets!="" AS got_comment 从 绳索 在buyer.id = corder.buyer 上左加入用户作为买家 LEFT JOIN invoice AS invoice ON invoice.id = corder.invoice LEFT JOIN invoice AS proforma ON proforma.id = corder.proforma 在哪里 corder.id 在 ( 选择编号 发件人 ORDER BY id DESC 限制 400,20 ) 订购方式 corder.id DESC;

此操作失败,并显示以下错误消息:

错误 1235 (42000): 这个版本的 MySQL 还不支持 'LIMIT & IN/ALL/ANY/SOME 子查询'

我使用的是 MySQL 5.1.61,它在 5.1 系列中相当新(显然 5.5.x 也不支持此功能)。

【问题讨论】:

  • 您在连接中使用的customer_order (invoice) 上没有索引吗?我的意思是,在生产系统中。使用 5 行表进行测试几乎没有价值。执行计划可能与实际不同(有很多行)。
  • 当我试图理解这一点时,我实际上尝试添加它,但它没有任何区别(如果你考虑一下,这是可以预料的)。是的,我在生产中这样做了——没有区别。
  • 第二,表是MyISAM还是InnoDB?有区别。
  • 最后,您的最后一个(有问题的)查询毫无用处。您(左)加入customer_invoice,但您不使用它作为选择。如果您发布有用的查询会更好。否则,为什么要使用仅是第二个的第 4 个(但可能有很多重复的行)?
  • MyISAM。如果您愿意,您可以在该查询中添加花里胡哨,这不会有任何区别(我显然选择的不仅仅是订单 ID)。

标签: mysql indexing sql-order-by left-join


【解决方案1】:

你能试试这个版本吗(它基本上首先获取corder 表的 420 行,保留其中的 20 行,然后进行 3 个外连接):

SELECT
    corder.id,
    corder.public_id,
    CONCAT(buyer.fname," ",buyer.lname) AS buyer_name,
    corder.status,
    corder.payment,
    corder.reserved AS R,
    corder.tracking_id != "" AS A,
    corder.payment_received AS pay_date,
    invoice.invoice_no AS inv,
    invoice.receipt_no AS rec,
    invoice.public AS pub_inv,
    proforma.proforma_no AS prof,
    proforma.public AS pub_pf,
    corder.rating,
    corder.rating_comments!="" AS got_comment
FROM
    ( SELECT * 
      FROM corder
      ORDER BY
        id DESC 
      LIMIT 400, 20
    )
    AS corder
LEFT JOIN user as buyer ON buyer.id = corder.buyer
LEFT JOIN invoice AS invoice ON invoice.id = corder.invoice
LEFT JOIN invoice AS proforma ON proforma.id = corder.proforma
ORDER BY
    corder.id DESC ;

【讨论】:

  • 0.09 秒,而之前是 12-14 秒。 是我之前说的很好的解决方案。向您致敬,先生!我很遗憾似乎没有办法强迫 MySQL 自己做这件事,但我想这已经是最好的了。
猜你喜欢
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 2023-02-25
  • 2010-11-16
  • 1970-01-01
  • 2019-08-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多