给出零售管理系统的以下表格:
商店: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:
要计算每个商店的广告资源的成本,我通过 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
正确设置所有外键和索引。问题是由于每个商店中的大量商店和移动,查询变得越来越重,并且因为库存是从每个商店的历史开始计算的,所以它随着时间的推移而变慢。
如何优化此方案?
答案 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}}。