5

这是给你的一个整洁的(显然是 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。那么,以所有好的和正确的名义,为什么它会突然切换到文件排序?!更重要的是,我该如何避免这种情况?作为记录,我很乐意接受一个书面答案,解释为什么不能避免(如果是这样的话。)

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

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

更新(评论概要)

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

  • *您应该在 customer_order.invoice 上添加一个键* - 我实际上在生产中尝试过,它没有区别(因为它不应该)
  • 你应该使用USE INDEX-- 试过了,没用。我也试过FORCE INDEX了——也没有结果(没有任何变化)
  • 您过度简化了用例,我们需要实际的生产查询——我可能在第一次迭代中剥离了太多,所以我更新了它(我只是, ci.invoice_noSELECT最后几个查询中添加了)。作为记录,如果有人真的很好奇,这里是生产查询,完全一样(检索订单的最后一页):
选择
    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_comments!="" 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(并且从子查询中的主查询复制字段是不优雅的),子查询最终可能会涉及大量数据,但不幸的是它似乎没有工作:

选择
    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_comments!="" 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 中也不支持)。

4

1 回答 1

5

你能试试这个版本吗(它基本上首先获取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 ;
于 2012-11-01T17:20:04.497 回答