零售库存Mysql查询优化

时间:2016-01-01 23:23:58

标签: php mysql query-optimization

给出零售管理系统的以下表格:

商店store_id, name

产品product_id, name, cost

PRODUCT_ENTRIES key, store_id, date

PRODUCT_ENTRIES_CONTENT: product_entries_key, product_id, quantity

PRODUCT_EXITS key, store_id, product_id, quantity, status, date

销售key, store_id, date

SALES_CONTENT: sales_key, product_id, quantity

退货key, store_id, date

RETURNS_CONTENT: returns_key, product_id, quantity

为了计算库存值,我会遍历产品表的内容和每个product_id:

  • product_entries_content以及returns_content
  • 的总和
  • 减去product_exits_content(其中status = 2或3)以及sales_content
  • 的数量

要计算每个商店的广告资源的成本,我通过 PHP循环为每个不同的商店运行以下查询并输出结果:

SELECT

    SUM((((

    (SELECT COALESCE(SUM(product_entries_content.quantity), 0)

    FROM product_entries

    INNER JOIN product_entries_content ON 
product_entries_content.product_entries_key = product_entries.key

    WHERE product_entries_content.product_id = products.id 
    AND product_entries.store_id = '.$row['id'].'   
    AND DATE(product_entries.date) <= DATE(NOW()))


    -

    (SELECT COALESCE(SUM(quantity), 0) 

    FROM sales_content

    INNER JOIN sales ON sales.key  = sales_content.sales_key

    WHERE product_id = products.product_id AND sales.store_id = '.$row['id'].'
    AND DATE(sales_content.date) <= DATE(NOW()))

    +

    (SELECT COALESCE(SUM(quantity), 0) 

    FROM returns_content

    INNER JOIN returns  ON returns.key = returns_content.returns_key

    WHERE product_id = products.product_id AND returns.store_id = '.$row['id'].'
    AND DATE(returns.date) <= DATE(NOW()))

    -

    (SELECT COALESCE(SUM(quantity), 0) 

    FROM product_exits

    WHERE product_id = products.product_id AND (status = 2 OR status = 3) 
AND product_exits.store_id = '.$row['id'].' #store_id
    AND DATE(product_exits.date) <= DATE(NOW()))     

    ) * products.cost) / 100) ) AS "'.$row['key'].'" #store_name

FROM products WHERE 1

正确设置所有外键和索引。问题是由于每个商店中的大量商店和移动,查询变得越来越重,并且因为库存是从每个商店的历史开始计算的,所以它随着时间的推移而变慢。

如何优化此方案?

1 个答案:

答案 0 :(得分:1)

理想情况下,每个表的SHOW CREATE TABLE tablename在任何优化问题中都会有很大帮助。每列的数据类型对性能非常重要。

也就是说,假设列数据类型都合适,从您提供的以下信息中可能会有所帮助。

添加以下索引(如果它们不存在)。重要信息:单列索引不是以下复合索引的有效替换。你声明了

  

正确设置了所有外键和索引。

但这并没有告诉我们它们是什么,以及它们是否适当&#34;优化。

新索引

ALTER TABLE sales
CREATE INDEX `aaaa` (`store_id`,`key`)

ALTER TABLE sales_content
CREATE INDEX `bbbb` (`product_id`,`sales_key`,`date`,`quantity`)

ALTER TABLE returns
CREATE INDEX `cccc` (`store_id`,`date`,`sales_key`)

ALTER TABLE returns_content
CREATE INDEX `dddd` (`product_id`,`returns_key`,`quantity`)

ALTER TABLE product_exits
CREATE INDEX `eeee` (`product_id`,`status`,`store_id`,`date`,`quantity`)

ALTER TABLE product_entries
CREATE INDEX `ffff` (`store_id`,`date`,`key`)

ALTER TABLE product_entries_content
CREATE INDEX `gggg` (`product_id`,`product_entries_key`,`quantity`)

(使用比aaaa更合适的名称。我只是用它们来节省时间。)

上述每个索引都允许数据库为每个表只读取一行。涉及连接的大多数性能问题来自所谓的双重查找。

了解索引和双重查找

索引只是表数据的副本。索引中列出的每个列都按照索引中列出的顺序从表中复制,然后主键将附加到索引中的该行。当数据库使用索引查找值时,如果索引中不包含所有信息,则主键将用于访问表的聚簇索引以获取其余信息。这就是双重查看,它对性能非常不利。

示例

以上所有索引都旨在避免双重查找。让我们看一下第二个子查询,看看与该查询相关的索引是如何工作的。

ALTER TABLE sales
CREATE INDEX `aaaa` (`store_id`,`key`)

ALTER TABLE sales_content
CREATE INDEX `bbbb` (`product_id`,`sales_key`,`date`,`quantity`)

子查询(我添加了别名并调整了日期列的访问方式,但是没有改变):

SELECT COALESCE(SUM(sc.quantity), 0) 
FROM sales_content sc
INNER JOIN sales s 
ON s.key  = sc.sales_key
WHERE sc.product_id = p.product_id 
AND s.store_id = '.$row['id'].'
AND sc.date < DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)

使用aaaa索引,数据库将只能查找sales表中与store_id匹配的那些行,因为它在索引中首先列出。可以像电话簿一样考虑这一点,其中store_id是姓,key是名字。如果你有姓氏,那么很容易翻到电话簿的那一点,并快速获得所有姓氏的名字。同样,数据库能够非常快速地翻转&#34;到包含给定store_id值的索引部分,并查找所有key值。在这种情况下,我们根本不需要主键(电话簿示例中的电话号码。)

所以,使用sales表格完成后,我们需要所有key值。

接下来,数据库移动到bbbb索引。我们已从主查询中获得product_id,并且我们拥有sales_key索引中的aaaa。这就像在电话簿中同时拥有名字和姓氏一样。唯一要比较的是日期,可能就像电话簿中的地址一样。数据库将按顺序存储所有日期,因此通过给它一个截止值,它可以查看到某一点的所有日期。

bbbb索引的最后一部分是数量,这样数据库可以快速总结所有这些数量。要了解为什么这么快,请再次考虑电话簿。想象一下,除了姓氏,名字和地址信息之外,还有一个数量列(某些东西,它并不重要)。如果您想要特定姓氏,名字和所有以5或更少数字开头的地址的数量总和,这很容易,不是吗?只需找到第一个,然后按顺序添加它们,直到到达第一个以大于5的数字开头的地址。数据库以这种方式使用日期列时的方式相同(日期就像地址列,在这个例子。)

日期列

最后,我之前提到过,我更改了日期列的访问方式。您永远不希望在要与另一个值进行比较的数据库列上运行函数。原因是:如果您在进行任何比较之前必须将所有地址转换为罗马数字,会发生什么?你不可能像我们之前那样沿着列表走下去。您必须转换所有值,然后检查每个值以确保它在限制范围内,因为我们不再知道值是否正确排序以便能够执行&#34;读取它们所有然后停在一定值#34;我在上面描述的快捷方式。

您和我可能知道将日期时间值转换为日期并不会改变顺序,但数据库不会知道(可能会优化此转换,但那不是我想要假设的东西。)所以,保持列纯净。我所做的更改只是采用NOW()日期,并添加一天,然后将其设为<而不是<=。毕竟,比较两个值并说日期必须等于或小于今天的日期相当于说日期时间必须小于明天的日期。

查询

以下是我的最终查询。如上所述,除了日期更改和别名之外,没有太多变化。但是,您在访问products.id的第一个子查询中输入了拼写错误。我将id更正为product_id,因为它与您所说的products表的列匹配。

SELECT
SUM(
(
(
(
    (
    SELECT COALESCE(SUM(pec.quantity), 0)
    FROM product_entries pe
    INNER JOIN product_entries_content pec 
    ON pec.product_entries_key = pe.key
    WHERE pec.product_id = p.product_id 
    AND pe.store_id = '.$row['id'].' 
    AND pe.date < DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
    )
    -
    (
    SELECT COALESCE(SUM(sc.quantity), 0) 
    FROM sales_content sc
    INNER JOIN sales s 
    ON s.key  = sc.sales_key
    WHERE sc.product_id = p.product_id 
    AND s.store_id = '.$row['id'].'
    AND sc.date < DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
    )
    +
    (
    SELECT COALESCE(SUM(rc.quantity), 0)
    FROM returns_content rc
    INNER JOIN returns r 
    ON r.key = rc.returns_key
    WHERE rc.product_id = p.product_id 
    AND r.store_id = '.$row['id'].'
    AND r.date < DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
    )
    -
    (
    SELECT COALESCE(SUM(pex.quantity), 0)
    FROM product_exits pex
    WHERE pex.product_id = p.product_id 
    AND (pex.status = 2 OR pex.status = 3)
    AND pex.store_id = '.$row['id'].' #store_id
    AND pex.date < DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
    )
) 
* p.cost) 
/ 100)
) AS "'.$row['key'].'" #store_name
FROM products p WHERE 1

您可以通过将product_exits表上的子查询拆分为2个单独的子查询,而不是使用OR来进一步优化这一点,而OR很多次都会表现不佳。最终,您必须对此进行基准测试,以了解数据库如何自行优化{{1}}。