【问题标题】:optimising a mysql query with 2 joins and group by clauses使用 2 个连接和 group by 子句优化 mysql 查询
【发布时间】:2017-01-10 14:32:49
【问题描述】:

我有一个需要 10-20 秒的查询,但我确信它可以优化,我只是不够好做它。我需要一些帮助和解释,以便我可以将其应用于类似的查询。 这是我的查询:

SELECT
        `store_formats`.`Store Nbr`,
        `store_formats`.`Store Name`,
        `store_formats`.`Format Name`,
        `eds_sales`.`Date`,
         sum(`eds_sales`.`EPOS Sales`) AS Sales,
         sum(`eds_sales`.`EPOS Quantity`) AS Quantity
         FROM
         `eds_sales`
         INNER JOIN `item_codes` ON `eds_sales`.`Prime Item Nbr` = `item_codes`.`Customer Item`
         INNER JOIN `store_formats` ON `eds_sales`.`Store Nbr` = `store_formats`.`Store Nbr`
         WHERE
         `eds_sales`.`Store Nbr` IN ($storenbr) AND
         `eds_sales`.`Date`  BETWEEN '$startdate' AND '$enddate' AND
         `eds_sales`.`Client` = '$customer' AND
         `eds_sales`.`Retailer` IN ($retailer) AND
         `store_formats`.`Format Name` IN ($storeformat) AND
         `item_codes`.`Item Number` IN ($products)
         GROUP BY
         `store_formats`.`Store Name`,
         `store_formats`.`Store Nbr`,
         `store_formats`.`Format Name`,
         `eds_sales`.`Date`

这里是解释输出:

正如您将看到的那样,我已经尝试并创建了一些索引,其中涉及的列并没有多大成功。我认为主要的延迟是由于复制到临时表造成的。

这些是涉及的表格:

存储格式:

CREATE TABLE `store_formats` (
`id` int(12) NOT NULL,
`Store Nbr` smallint(5) UNSIGNED DEFAULT NULL,
`Store Name` varchar(27) DEFAULT NULL,
`City` varchar(19) DEFAULT NULL,
`Post Code` varchar(9) DEFAULT NULL,
`Region #` int(2) DEFAULT NULL,
`Region Name` varchar(10) DEFAULT NULL,
`Distr #` int(3) DEFAULT NULL,
`Dist Name` varchar(26) DEFAULT NULL,
`Square Footage` varchar(7) DEFAULT NULL,
`Format` int(1) DEFAULT NULL,
`Format Name` varchar(23) DEFAULT NULL,
`Store Type` varchar(20) DEFAULT NULL,
`TV Region` varchar(12) DEFAULT NULL,
`Pharmacy` varchar(3) DEFAULT NULL,
`Optician` varchar(3) DEFAULT NULL,
`Home Shopping` varchar(3) DEFAULT NULL,
`Retailer` varchar(15) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `store_formats`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `uniqness` (`Store Nbr`,`Store Name`,`Format`),
ADD KEY `Store Nbr_2` (`Store Nbr`,`Format Name`,`Store Name`);

eds_sales:

CREATE TABLE `eds_sales` (
`id` int(12) UNSIGNED NOT NULL,
`Prime Item Nbr` mediumint(7) NOT NULL,
`Prime Item Desc` varchar(255) NOT NULL,
`Prime Size Desc` varchar(255) NOT NULL,
`Variety` varchar(255) NOT NULL,
`WHPK Qty` int(5) NOT NULL,
`SUPPK Qty` int(5) NOT NULL,
`Depot Nbr` int(5) NOT NULL,
`Depot Name` varchar(50) NOT NULL,
`Store Nbr` smallint(5) UNSIGNED NOT NULL,
`Store Name` varchar(255) NOT NULL,
`EPOS Quantity` smallint(3) NOT NULL,
`EPOS Sales` decimal(13,2) NOT NULL,
`Date` date NOT NULL,
`Client` varchar(10) NOT NULL,
`Retailer` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `eds_sales`
ADD UNIQUE KEY `uniqness` (`Prime Item Nbr`,`Prime Item Desc`,`Prime Size Desc`,`Variety`,`WHPK Qty`,`SUPPK Qty`,`Depot Nbr`,`Depot Name`,`Store Nbr`,`Store Name`,`Date`,`Client`) USING BTREE,
ADD KEY `Store Nbr` (`Store Nbr`),
ADD KEY `Prime Item Nbr_2` (`Prime Item Nbr`,`Date`),
ADD KEY `id` (`id`) USING BTREE,
ADD KEY `Store Nbr_2` (`Prime Item Nbr`,`Store Nbr`,`Date`,`Client`,`Retailer`) USING BTREE,
ADD KEY `Client` (`Client`,`Store Nbr`,`Date`),
ADD KEY `Date` (`Date`,`Client`,`Retailer`);

项目代码:

CREATE TABLE `item_codes` (
`id` int(12) NOT NULL,
`Item Number` varchar(30) CHARACTER SET latin1 NOT NULL,
`Customer Item` mediumint(7) NOT NULL,
`Description` varchar(255) CHARACTER SET latin1 NOT NULL,
`Status` varchar(15) CHARACTER SET latin1 NOT NULL,
`Customer` varchar(30) CHARACTER SET latin1 NOT NULL,
`Sort Name` varchar(255) CHARACTER SET latin1 NOT NULL,
`EquidataCustomer` varchar(30) CHARACTER SET latin1 NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `item_codes`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `uniq` (`Item Number`,`Customer Item`,`Customer`,`EquidataCustomer`),
ADD KEY `Item Number_2` (`Item Number`,`Sort Name`,`EquidataCustomer`),
ADD KEY `Customer Item` (`Customer Item`,`Item Number`,`Sort Name`,`EquidataCustomer`),
ADD KEY `Customer Item_2` (`Customer Item`,`Item Number`,`EquidataCustomer`);

所以我的问题: 如您所见,我正在加入 3 个表格,并且我正在按日期按商店格式查找销售额。我一直在尝试不同类型的连接,或者例如,不是将销售连接到 item_codes 和 store_formats,而是将 store_formats 连接到其他连接,但结果相同。我还使用 IN 传递了一些变量数组,因为这些变量由应用程序中的选择框提供。

  1. 加入这些表的最佳方式
  2. 建议每个表的最佳索引
  3. 为什么我会得到临时表?是因为group by吗?有解决方法吗?
  4. 如果需要临时表,是否可以加快创建速度? (我已经在 8 个磁盘的 RAID 中拥有数据文件夹,但仍然很慢。
  5. 当然欢迎任何建议的替代方案

更新:用 cmets 的一些建议更新了我的表格

更新:修改 my.cnf 以提高性能(我的 RAM 为 8GB,2 核,/data/tmp 位于 8 驱动器 raid 上,与数据所在的位置相同)

tmpdir          = /dev/shm/:/data/tmp:/tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
expire_logs_days        = 10
max_binlog_size   = 100M
innodb_buffer_pool_size = 6G
innodb_buffer_pool_instances = 6
query_cache_type=1

【问题讨论】:

  • 这个问题可能会在DBA exchange 上得到更多专家的关注。
  • 您能否请edit 显示您表上的所有索引?
  • 为问题添加索引
  • 为了爱人类,不要在表格/列标识符中使用空格。
  • 尝试减少具有大量记录的表中的 varchar 长度,我猜您的情况可能不需要 255。删除所有索引并仅创建 JOINS/WHERE/GROUP/ORDER BY 所需的索引,也尝试使用外键(这会强制数据完整性和相同的格式/长度)。为了进行测试,我会在查询中添加一些常量,例如 WHERE somecolumn in (1,2,3),然后将其替换为具体条件。我看到查询正在使用索引,但可能有很多冗余,有时它们可​​能不适合 RAM 内存

标签: mysql indexing query-optimization mariadb temp-tables


【解决方案1】:

(评论太多了;请原谅我使用答案。)

当您有INDEX(a)INDEX(a,b) 时,前者是多余的,应该删除。我看到了大约 5 个这样的案例。

每个store_nbr 恰好有一个store_name?如果是这样,在多个表中拥有store_name 是多余的。我不知道store_formats 的意图,但我想那是容纳store_name 的一张桌子。请注意,两个 store_name 列和 store_nbr 列的数据类型大小不一致!

似乎每个商店都应该有一个唯一的数字,如果是这样,那么 ADD UNIQUE KEY uniqness (Store Nbr,Store Name) 应该可能变成PRIMARY KEY(store_nbr)。 (抱歉,我不会在你的列名中添加空格。)

以日期开头的索引很少有用,因此请去掉 KEY Date_2 (Date,Client)。在其位置添加INDEX(Client, store_nbr, Date);这应该对查询速度有直接影响。您可能会看到 EXPLAIN SELECT... 发生变化。

int(4)——也许你的意思是SMALLINT UNSIGNED

UNIQUE(或PRIMARY)键中包含Date 通常是“错误的”。 “客户”在同一天购买了两次相同的东西?

在你做出这些改变之后,让我们再谈一谈。

为了观看的一致性,请提供SHOW CREATE TABLE

避免这种结构:

FROM ( SELECT ... )
JOIN ( SELECT ... ) ON ...

这是低效的,因为两个子查询都没有使JOIN 有效的索引。

【讨论】:

  • 嗨,谢谢你的第一点,不知道它是如何工作的。对于重复的列,这些列来自 excel 文件,直接在 mysql 中导入。我计划在将来删除它们,但现在在一些快速查询中对它们有一些用处。对于唯一性,如果我将此唯一键转换为主键,它仍然是唯一的吗?用日期更改索引。修改数据类型以匹配所有表。对于唯一的日期,它需要是这样的,因为我不是与客户打交道,而是为商店提供数据,所以唯一性是在 date-storenbr 和其他人上定义的
  • 我将在明天发布我的表演创建表和表演。谢谢
  • A PRIMARY KEYUNIQUE 键是 INDEX
【解决方案2】:

将选择移动到子查询中以最小化连接项目。我相信 MySQL 已经为你做到了。我会检查该信息的执行计划。

SELECT
  stores.nbr, stores.name, stores.format,
  epos.date,
  sum(epos.sales) AS Sales,
  sum(epos.qty) AS Quantity
FROM
  (SELECT `Date` as `date`, `EPOS Sales` as sales,`EPOS Quantity` as qty, `Prime Item Nbr` as item_number, `Store Nbr` as store_number
FROM
  `eds_sales`
WHERE
  `eds_sales`.`Store Nbr` IN ($storenbr) AND
  `eds_sales`.`Date`  BETWEEN '$startdate' AND '$enddate' AND
  `eds_sales`.`Client` = '$customer' AND
  `eds_sales`.`Retailer` IN ($retailer)) as epos

  INNER JOIN 

   (SELECT `Customer Item` as custItem
   FROM `item_codes`
   WHERE
     `item_codes`.`Item Number` IN ($products)) as items ON epos.item_number = items.custItem

  INNER JOIN 

    (SELECT `Store Nbr` as nbr, `Store Name` as name, `Format Name` as format
   FROM
     `store_formats`
   WHERE
     `store_formats`.`Format Name` IN ($storeformat)) as stores ON epos.store_number = stores.nbr
GROUP BY
  stores.name,
  stores.nbr,
  stores.format,
  epos.date

【讨论】:

    【解决方案3】:

    将连接表上的条件从WHERE 子句移动到连接的ON 子句:

    SELECT
    `store_formats`.`Store Nbr`,
    `store_formats`.`Store Name`,
    `store_formats`.`Format Name`,
    `eds_sales`.`Date`,
    sum(`eds_sales`.`EPOS Sales`) AS Sales,
    sum(`eds_sales`.`EPOS Quantity`) AS Quantity
    FROM `eds_sales`
    JOIN `item_codes`
      ON `eds_sales`.`Prime Item Nbr` = `item_codes`.`Customer Item`
      AND `item_codes`.`Item Number` IN ($products)
    JOIN `store_formats`
      ON `eds_sales`.`Store Nbr` = `store_formats`.`Store Nbr`
      AND `store_formats`.`Format Name` IN ($storeformat)
    WHERE `eds_sales`.`Store Nbr` IN ($storenbr)
    AND `eds_sales`.`Date` BETWEEN '$startdate' AND '$enddate'
    AND `eds_sales`.`Client` = '$customer'
    AND `eds_sales`.`Retailer` IN ($retailer)
    GROUP BY
     `store_formats`.`Store Name`,
     `store_formats`.`Store Nbr`,
     `store_formats`.`Format Name`,
     `eds_sales`.`Date`
    

    创建以下索引:

    CREATE INDEX IDX001 ON eds_sales (Client,`Store Nbr`,`Retailer`,`Date`);
    CREATE INDEX IDX002 ON store_formats (`Store Nbr`,`Format Name`);
    

    如果有效,请告诉我,我会解释原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      相关资源
      最近更新 更多