【问题标题】:How to optimize the SQL query that joins 3 table如何优化连接3表的SQL查询
【发布时间】:2016-04-15 23:19:32
【问题描述】:

你好,

我有以下 SQL 查询,运行大约需要 4 秒:

select 
    o.id, tu.status_type, m.upload_date
from 
    (select order_id, max(last_updated) as maxudate from tracking_update group by order_id) t
inner join 
    tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.last_updated
right join 
    fgw247.order o on t.order_id=o.id
left join 
    manifest m on o.manifest_id=m.id
where 
    (m.upload_date >= '2015-12-12 00:00:00') or (m.upload_date <='2015-12-12 00:00:00' and tu.status_type != 'D' and tu.status_type != 'XD')

查询连接以下 3 个表:

订单、清单和 Tracking_Update。

查询将返回满足以下条件的订单:

  1. 订单
  2. 订单 > 30 天,而不是交货状态

如果该订单的最新 tracking_update 的 status_type 为“D”或“XD”,则该订单被视为已送达

现在,订单列在订单表中。 Order 表有一个名为 manifest_id 的列,它引用了在上传订单时创建的 Manifest。

一个清单可以有多个订单。这用于确定过去 30 天内是否已上传订单。

最后, tracking_update 表包含每个订单的 tracking_updates。一个订单可以有多个 tracking_updates。

目前,tracking_update 表的记录长度超过 1M。

下面列出了每个表的创建语句:

CREATE TABLE "order" (
  "id" int(11) NOT NULL AUTO_INCREMENT,
  "ShipmentId" varchar(50) DEFAULT NULL,
  "RecipientName" varchar(160) DEFAULT NULL,
  "CompanyName" varchar(160) DEFAULT NULL,
  "Address1" varchar(160) DEFAULT NULL,
  "Address2" varchar(160) DEFAULT NULL,
  "City" varchar(50) DEFAULT NULL,
  "State" varchar(3) DEFAULT NULL,
  "ZIP" int(11) DEFAULT NULL,
  "TEL" int(11) DEFAULT NULL,
  "Email" varchar(255) DEFAULT NULL,
  "Bottles" int(11) DEFAULT NULL,
  "Weight" float DEFAULT NULL,
  "Resi" tinyint(1) DEFAULT NULL,
  "Wave" int(11) DEFAULT NULL,
  "url_slug" varchar(50) DEFAULT NULL,
  "manifest_id" int(11) DEFAULT NULL,
  "shipment_date" datetime DEFAULT NULL,
  "tracking_number" varchar(30) DEFAULT NULL,
  "shipping_carrier" varchar(10) DEFAULT NULL,
  "last_tracking_update" datetime DEFAULT NULL,
  "delivery_date" datetime DEFAULT NULL,
  "customer_code" varchar(50) DEFAULT NULL,
  "sub_cust_code" int(11) DEFAULT NULL,
  PRIMARY KEY ("id"),
  UNIQUE KEY "ShipmentID" ("ShipmentId"),
  KEY "manifest_id" ("manifest_id"),
  KEY "order_idx3" ("tracking_number"),
  KEY "order_idx4" ("customer_code"),
  CONSTRAINT "order_ibfk_1" FOREIGN KEY ("manifest_id") REFERENCES "manifest" ("id")
);

Tracking_Update 表:

CREATE TABLE "tracking_update" (
  "id" int(11) NOT NULL AUTO_INCREMENT,
  "order_id" int(11) DEFAULT NULL,
  "ship_update_date" datetime DEFAULT NULL,
  "message" varchar(400) DEFAULT NULL,
  "location" varchar(100) DEFAULT NULL,
  "status_type" varchar(2) DEFAULT NULL,
  "last_updated" datetime DEFAULT NULL,
  "hash" varchar(32) DEFAULT NULL,
  PRIMARY KEY ("id"),
  KEY "order_id" ("order_id"),
  KEY "tracking_update_idx2" ("status_type"),
  KEY "tracking_update_idx3" ("ship_update_date"),
  CONSTRAINT "tracking_update_ibfk_1" FOREIGN KEY ("order_id") REFERENCES "order" ("id")
);

清单表:

CREATE TABLE "manifest" (
  "id" int(11) NOT NULL AUTO_INCREMENT,
  "upload_date" datetime DEFAULT NULL,
  "name" varchar(100) DEFAULT NULL,
  "destination_gateway" varchar(40) DEFAULT NULL,
  "arrived" tinyint(1) DEFAULT NULL,
  "customer_code" varchar(50) DEFAULT NULL,
  "upload_user" varchar(50) DEFAULT NULL,
  "trip_id" int(11) DEFAULT NULL,
  PRIMARY KEY ("id")
);

这里也是用 JSON 导出的 select 语句的解释:

{
    "data":
    [
        {
            "id": 1,
            "select_type": "PRIMARY",
            "table": "m",
            "type": "ALL",
            "possible_keys": "PRIMARY",
            "key": null,
            "key_len": null,
            "ref": null,
            "rows": 220,
            "Extra": "Using where"
        },
        {
            "id": 1,
            "select_type": "PRIMARY",
            "table": "o",
            "type": "ref",
            "possible_keys": "manifest_id",
            "key": "manifest_id",
            "key_len": "5",
            "ref": "fgw247.m.id",
            "rows": 246,
            "Extra": "Using index"
        },
        {
            "id": 1,
            "select_type": "PRIMARY",
            "table": "tu",
            "type": "ref",
            "possible_keys": "order_id",
            "key": "order_id",
            "key_len": "5",
            "ref": "fgw247.o.id",
            "rows": 7,
            "Extra": "Using where"
        },
        {
            "id": 1,
            "select_type": "PRIMARY",
            "table": "<derived2>",
            "type": "ref",
            "possible_keys": "<auto_key0>",
            "key": "<auto_key0>",
            "key_len": "11",
            "ref": "fgw247.o.id,fgw247.tu.last_updated",
            "rows": 13,
            "Extra": "Using index"
        },
        {
            "id": 2,
            "select_type": "DERIVED",
            "table": "tracking_update",
            "type": "index",
            "possible_keys": "order_id",
            "key": "order_id",
            "key_len": "5",
            "ref": null,
            "rows": 1388275,
            "Extra": null
        }
    ]
}

感谢任何帮助

更新

这是我正在使用的当前查询,速度要快得多:

SELECT 
    o.*, tu.*
FROM
    fgw247.`order` o
JOIN
    manifest m
ON
    o.`manifest_id` = m.`id` 

JOIN
    `tracking_update` tu
ON
    tu.`order_id` = o.`id` and tu.`ship_update_date` = (select max(last_updated) as last_updated from tracking_update where order_id = o.`id` group by order_id)
WHERE
    m.`upload_date` >= '2015-12-14 11:50:12' 
    OR 
        (o.`delivery_date` IS NULL AND m.`upload_date` < '2015-12-14 11:50:12')
LIMIT 100

【问题讨论】:

    标签: mysql join inner-join right-join


    【解决方案1】:

    考虑以下选项来优化查询的执行:

    1. 联接类型:您确定需要左右联接吗?您要报告甚至未包含在清单中的订单吗?如果不是,则使用内连接而不是左右连接。

    2. 附加索引:manifest.upload_date 字段上没有索引,您应该添加一个。我还将在 tracking_update 表的 order_id、update_date 和 status_type 字段(按此顺序!)上创建一个复合索引。

    3. 在 where 中使用 union 代替 or 标准:Mysql 传统上并不擅长优化 where 子句中的 or 标准。所以将(m.upload_date &gt;= '2015-12-12 00:00:00') or (m.upload_date &lt;='2015-12-12 00:00:00' and tu.status_type != 'D' and tu.status_type != 'XD') 变成一个有 2 个查询的联合。 2 个查询将与现有查询相同,直到 where 部分。第一个查询在 where 子句中只有 (m.upload_date &gt;= '2015-12-12 00:00:00') 条件,而第二个查询将有 (m.upload_date &lt;='2015-12-12 00:00:00' and tu.status_type != 'D' and tu.status_type != 'XD') 条件。

    如果您添加新索引,请通过运行新的说明检查查询是否使用它们。

    【讨论】:

    • 感谢您的回复。请查看我更新的查询。我仍然不确定我是否了解工会。你是说创建两个不同的选择语句?
    • 您将联接更改为内部联接(这是我的第一个建议),更改了 where 条件(我无法对此发表评论,因为我不知道您的数据),并从派生表更改了子查询重视要求。我不知道哪个更改加快了新版本的速度。
    • 我知道我更喜欢派生表版本而不是您的选择,因为在派生表的情况下,子查询只执行一次。对于您的版本,存在单独执行每条记录的子查询的风险。但是,如果连接会消除派生表中的大部分记录,那么更新版本中的解决方案也可以很快。我不知道你的数据,所以也不能对此发表评论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-16
    • 2020-08-08
    • 2017-04-24
    • 2018-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多