【问题标题】:SQL query running extremely slowSQL 查询运行速度极慢
【发布时间】:2016-02-29 00:54:35
【问题描述】:

我有一个 sql 查询,用于收集库存中所有产品的年初至今总计。这个查询运行很快,一秒多一点就返回了大约 5000 个结果。

使用的查询是

SELECT `ITINCODE` as Code, `IT_product_id` as ID,
SUM(IF(YEAR(`ITDATE`) = YEAR(CURDATE() ), IF(ITTYPE = "O",`ITVAL`, -`ITVAL` ), 0) ) as 'YTD_VAL'
FROM `FITEMS`
WHERE (ITTYPE = 'O' OR `ITTYPE` = 'R') AND ITBACKORDER = 'N' AND ITAMNT > 0 AND YEAR(`ITDATE`) >= YEAR(CURDATE() ) -1
GROUP BY `ITINCODE`
ORDER BY YTD_VAL DESC

我想获取 YTD_VAL 中的值并将它们存储在实际产品表中,以便它们可以用作其他查询的 ORDER BY。我在 products 表中添加了一个名为 ytd_val 的新字段,然后运行

UPDATE products p SET p.ytd_val = 
(SELECT SUM(IF(YEAR(`ITDATE`) = YEAR(CURDATE() ), IF(ITTYPE = 'O',`ITVAL`, -`ITVAL` ), 0) ) as 'YTD_VAL'
FROM `FITEMS`
WHERE ITINCODE  = p.products_model AND (ITTYPE = 'O' OR `ITTYPE` = 'R') AND ITBACKORDER = 'N' AND ITAMNT > 0 AND YEAR(`ITDATE`) >= YEAR(CURDATE() ) -1
GROUP BY `ITINCODE`
ORDER BY YTD_VAL DESC)

我们的想法是每晚通过 cron 作业运行它,以便更新值以反映前几天的销售额。

但是,运行此查询需要 10 多分钟,但仍未完成。

FITEMS 表中的 ITINCODE 与 products 表中的 products_model 相同。 FITEMS 表中的 IT_product_id 与 products 表中的 products_id 相同。

如何加快查询速度?我认为由于原始结果查询返回的速度足够快,因此简单地更新另一个表上的值会花费几秒钟的时间!

表结构如下:

show create table fitems\G;

Create Table: CREATE TABLE `fitems` (
  `ITUNIQUEREF` int(11) unsigned NOT NULL,
  `ITAMNT` int(11) NOT NULL,
  `ITREF` int(11) unsigned NOT NULL,
  `ITTYPE` char(1) NOT NULL,
  `ITVAL` decimal(10,4) NOT NULL,
  `ITVAT` decimal(10,4) NOT NULL,
  `ITPRICE` decimal(10,4) NOT NULL,
  `ITDATE` date NOT NULL,
  `ITBACKORDER` char(1) NOT NULL,
  `ITDISC` decimal(10,2) NOT NULL,
  `ITORDERREF` int(11) NOT NULL,
  `ITTREF` int(11) unsigned NOT NULL,
  `ITDATEDLY` date NOT NULL,
  `ITINCODE` char(20) NOT NULL,
  `IT_product_id` int(11) unsigned NOT NULL,
  `ITBUILT` int(11) NOT NULL,
  PRIMARY KEY (`ITUNIQUEREF`),
  KEY `ITREF` (`ITREF`,`ITTYPE`,`ITDATE`,`ITBACKORDER`,`ITORDERREF`,`ITDATEDLY`,`ITINCODE`,`IT_product_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

.

show create table products\G;

CREATE TABLE `products` (
  `products_id` int(11) NOT NULL,
  `products_type` int(11) NOT NULL DEFAULT '1',
  `products_quantity` float NOT NULL DEFAULT '0',
  `products_model` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
  `products_image` varchar(64) CHARACTER SET utf8 DEFAULT NULL,
  `products_price` decimal(15,4) NOT NULL DEFAULT '0.0000',
  `products_group_a_price` decimal(15,4) NOT NULL,
  `products_group_b_price` decimal(15,4) NOT NULL,
  `products_group_c_price` decimal(15,4) NOT NULL,
  `products_group_d_price` decimal(15,4) NOT NULL,
  `products_group_e_price` decimal(15,4) NOT NULL,
  `products_group_f_price` decimal(15,4) NOT NULL,
  `products_group_g_price` decimal(15,4) NOT NULL,
  `products_virtual` tinyint(1) NOT NULL DEFAULT '0',
  `products_date_added` datetime NOT NULL DEFAULT '0001-01-01 00:00:00',
  `products_last_modified` datetime DEFAULT NULL,
  `products_date_available` datetime DEFAULT NULL,
  `products_weight` float NOT NULL DEFAULT '0',
  `products_status` tinyint(1) NOT NULL DEFAULT '0',
  `products_tax_class_id` int(11) NOT NULL DEFAULT '0',
  `manufacturers_id` int(11) DEFAULT NULL,
  `products_ordered` float NOT NULL DEFAULT '0',
  `products_quantity_order_min` float NOT NULL DEFAULT '1',
  `products_quantity_order_units` float NOT NULL DEFAULT '1',
  `products_priced_by_attribute` tinyint(1) NOT NULL DEFAULT '0',
  `product_is_free` tinyint(1) NOT NULL DEFAULT '0',
  `product_is_call` tinyint(1) NOT NULL DEFAULT '0',
  `products_quantity_mixed` tinyint(1) NOT NULL DEFAULT '0',
  `product_is_always_free_shipping` tinyint(1) NOT NULL DEFAULT '0',
  `products_qty_box_status` tinyint(1) NOT NULL DEFAULT '1',
  `products_quantity_order_max` float NOT NULL DEFAULT '0',
  `products_sort_order` int(11) NOT NULL DEFAULT '0',
  `products_canonical` text COLLATE utf8_unicode_ci NOT NULL,
  `products_discount_type` tinyint(1) NOT NULL DEFAULT '0',
  `products_discount_type_from` tinyint(1) NOT NULL DEFAULT '0',
  `products_price_sorter` decimal(15,4) NOT NULL DEFAULT '0.0000',
  `master_categories_id` int(11) NOT NULL DEFAULT '0',
  `products_mixed_discount_quantity` tinyint(1) NOT NULL DEFAULT '1',
  `metatags_title_status` tinyint(1) NOT NULL DEFAULT '0',
  `metatags_products_name_status` tinyint(1) NOT NULL DEFAULT '0',
  `metatags_model_status` tinyint(1) NOT NULL DEFAULT '0',
  `metatags_price_status` tinyint(1) NOT NULL DEFAULT '0',
  `metatags_title_tagline_status` tinyint(1) NOT NULL DEFAULT '0',
  `pricing_group` varchar(16) COLLATE utf8_unicode_ci DEFAULT NULL,
  `ytd_val` int(20) NOT NULL,
  PRIMARY KEY (`products_id`),
  KEY `idx_products_date_added_zen` (`products_date_added`),
  KEY `idx_products_status_zen` (`products_status`),
  KEY `idx_products_date_available_zen` (`products_date_available`),
  KEY `idx_products_ordered_zen` (`products_ordered`),
  KEY `idx_products_model_zen` (`products_model`),
  KEY `idx_products_price_sorter_zen` (`products_price_sorter`),
  KEY `idx_master_categories_id_zen` (`master_categories_id`),
  KEY `idx_products_sort_order_zen` (`products_sort_order`),
  KEY `idx_manufacturers_id_zen` (`manufacturers_id`),
  KEY `products_price` (`products_price`),
  KEY `products_status_products_price` (`products_status`,`products_price`),
  FULLTEXT KEY `idx_enhanced_products_model` (`products_model`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

【问题讨论】:

  • 显示每个的创建表语句
  • @Drew 我已经添加了创建表语句,以及有问题的两个表的主键信息。
  • 该表过于复杂,并试图完成几个表的工作。例如,列products_group_a_priceproducts_group_g_price 尖叫,“如果你需要一个H 怎么办?”您还使用了太多索引,这会减慢更改表的操作。见stackoverflow.com/questions/4120160/mysql-too-many-indexes
  • 索引是 zen cart 的作者设置的索引,可能还有一些其他的索引是以前的开发者添加的。如果我向您展示香草安装中的索引数量,您会感到震惊。该产品表中的所有第 9 栏字段都是 zen cart 中的原始字段。要开始拆分表,可能需要跨越数百个文件的大量工作。我同意,该结构还有很多不足之处,但在该级别进行更改会导致每次更新核心时都非常复杂。

标签: mysql


【解决方案1】:

SELECT 总是比UPDATE 快得多。

加快更新速度:

  • 查看您正在更新的表上的索引:它们都需要吗?如果没有,请删除不需要的(我至少会删除idx_products_status_zen,因为products_status_products_price 也涵盖了这一点)

  • 查看数据模型:您可以对正在更新的表进行分区吗?如果是这样,那将加快更新速度,因为您要更新的索引会更小,因此更新速度会更快;

  • 使用 InnoDB。它更快;

  • 您需要符合 ACID 标准吗?如果没有,那就改变 InnoDB 的设置来加速系统。

  • 如果你真的在使用 MySQL:切换到 MariaDB:大约快 8%;

  • 安装监控以查看瓶颈在哪里:IO 或 CPU:如果是读取时的 IO,则尝试压缩。

【讨论】:

  • 他在 MyISAM 上。他的全文搜索索引可能会根据版本要求使用 MyISAM。您对那个索引当然是正确的
  • 好的,感谢@Drew:请拆分表格以便单独管理全文索引。
  • 诺伯特干得好。好久没见了:)
  • @Drew :是的,重复的cmets:请阅读我写的内容,答案就在那里
【解决方案2】:

如果您对首次查询性能感到满意。

您可以将第二个查询转换为使用INNER JOIN,例如:

http://sqlfiddle.com/#!9/1028a/1

UPDATE products p 
INNER JOIN (
  SELECT SUM(IF(YEAR(`ITDATE`) = YEAR(CURDATE() ), IF(ITTYPE = 'O',`ITVAL`, -`ITVAL` ), 0) ) as 'YTD_VAL',
  ITINCODE
FROM `FITEMS`
WHERE (ITTYPE = 'O' OR `ITTYPE` = 'R') 
  AND ITBACKORDER = 'N' AND ITAMNT > 0 AND YEAR(`ITDATE`) >= YEAR(CURDATE() ) -1
GROUP BY `ITINCODE`
  ) t
ON t.ITINCODE  = p.products_model
SET p.ytd_val = t.YTD_VAL

更新顺便说一句,你不需要ORDER BY YTD_VAL DESC,我猜在这种特殊情况下它没有任何意义。

更新 2

UPDATE products p 
    INNER JOIN (
      SELECT SUM(IF(YEAR(`ITDATE`) = YEAR(CURDATE() ), IF(ITTYPE = 'O',`ITVAL`, -`ITVAL` ), 0) ) as 'YTD_VAL',
      IT_product_id 
    FROM `FITEMS`
    WHERE (ITTYPE = 'O' OR `ITTYPE` = 'R') 
      AND ITBACKORDER = 'N' AND ITAMNT > 0 AND YEAR(`ITDATE`) >= YEAR(CURDATE() ) -1
    GROUP BY `IT_product_id`
      ) t
    ON t.IT_product_id = p.products_id 
    SET p.ytd_val = t.YTD_VAL

【讨论】:

  • 奇怪的是,原始查询返回 4150 行,但 UPDATE 查询仅报告 2803 受影响的行。我在这里暗中尝试,但可能并非所有原始 ITINCODE 行都有匹配的 products_model。我将尝试改用 IT_products_id 和 products_id
  • @StevePrice 。 . .您的原始代码将不匹配的products_model 值设置为NULL。此版本不会更改它们。
  • 我的意思是原始查询只收集数据,不更新另一个表,返回 4150 个结果。可以假设如果有 4150 个结果可用,那么应该更新该数量的结果。
  • 请给我看SELECT COUNT(DISTINCT products_model) FROM products的结果?
  • 顺便问一下,您的查询速度提高了吗?
【解决方案3】:

我不是说要这样做的人,但考虑一下覆盖索引的情况。特别是在桌子上fitems。对于该查询,这些列相对较小。我会吓一跳来尝试特定的列。我们已经看到可以在很短的时间内完成非常长的查询的情况。但没有承诺。啊,我看你有一些。正在查看其下方的更改表的第一个编辑。我会继续寻找。

覆盖索引

覆盖索引是一种可以通过快速浏览索引 b 树来解决查询的索引,无需查找实际的表数据页面.这些是速度的终极必杀技。 Percona快文就可以了。

解释

并通过Explain 运行查询(更新)并检查输出。另见文章Using Explain to Write Better Mysql Queries

注意,其中一些 cmets 是针对后面的,不一定是这个 op。他似乎把他的房子收拾得井井有条。

【讨论】:

    【解决方案4】:

    在您的子查询中,order bygroup by 都不是必需的,因此 update 可以写成:

    UPDATE products p
        SET p.ytd_val = (SELECT SUM(IF(YEAR(`ITDATE`) = YEAR(CURDATE() ), IF(ITTYPE = 'O',`ITVAL`, -`ITVAL` ), 0) )
                         FROM `FITEMS`
                         WHERE FITEMS.ITINCODE = p.products_model AND
                               ITTYPE IN ('O', 'R') AND
                               ITBACKORDER = 'N' AND
                               ITAMNT > 0 AND
                               YEAR(`ITDATE`) >= YEAR(CURDATE() ) - 1
                        );
    

    为此,您需要FITEMS(ITINCODE, ITBACKORDER, ITTYPE, ITAMNT, ITVAL) 上的索引。这可能会显着加快您的查询速度。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-29
      相关资源
      最近更新 更多