我有以下查询
SELECT o.order_id,
p.pre_sale_phone_manual_id AS id,
p.created,
p.user_id
FROM `order` o
LEFT JOIN `customer` c ON c.customer_id = o.customer_id,
`pre_sale_phone_manual` p
LEFT JOIN `pre_sale_phone_manual` p1 ON p.pre_sale_phone_manual_id=p1.pre_sale_phone_manual_id
AND p.created > p1.created
WHERE p1.user_id IS NULL
AND p.phone <> ""
AND REPLACE(REPLACE(REPLACE(REPLACE(c.phone, "-", ""), ".", ""), "+", ""), " ", "") LIKE CONCAT('%', RIGHT(REPLACE(REPLACE(REPLACE(REPLACE(p.phone, "-", ""), ".", ""), "+", ""), " ", ""), 10))
AND o.created > p.created
AND o.created < (DATE_ADD(p.created, INTERVAL 183 DAY))
AND o.created > '2013-12-30 08:28:37'
查询基本上是匹配客户的电话号码和pre_sale_phone_manual表中的条目。 pre_sale_phone_manual的记录应该在订单日期之前,并且应该在6个月(183天)之内并且应该与pre_sale_phone_manual表的第一个条目匹配,因为其他用户可能存在重复的条目。
由于我发现订单表和pre_sale_phone_manual表之间的连接速度缓慢,因为没有1到1的连接并扫描整个表格,显然是INTERVAL 183 DAY
以下是EXPLAIN for query
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: o
type: ALL
possible_keys: order_created_index,fk_order_customer
key: NULL
key_len: NULL
ref: NULL
rows: 110658
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: p
type: ALL
possible_keys: created,phone
key: NULL
key_len: NULL
ref: NULL
rows: 2053
Extra: Using where; Using join buffer
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: p1
type: eq_ref
possible_keys: PRIMARY,created
key: PRIMARY
key_len: 4
ref: 463832_yii_adm_t4f.p.pre_sale_phone_manual_id
rows: 1
Extra: Using where; Not exists
*************************** 4. row ***************************
id: 1
select_type: SIMPLE
table: c
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: 463832_yii_adm_t4f.o.customer_id
rows: 1
Extra: Using where
以下统计信息来自mysql慢查询日志
Query_time: 126.038395 Lock_time: 0.000303 Rows_sent: 72 Rows_examined: 15266616
以下字段已编入索引,
order.created
pre_sale_phone_manual.created
pre_sale_phone_manual.phone
and PKs and FKs with _id suffix
请帮助您优化查询并感谢您的时间。
答案 0 :(得分:3)
有一些表演&#34;杀手&#34;:
customer
)* num-rows-of(pre_sale_phone_manual
)pre_sale_phone_manual
中找到每部手机的第一条记录
醇>
(您是否尝试在pre_sale_phone_manual
中为每部手机找到第一条记录?
我认为这是代码的作用,所以我认为情况就是如此。)
我无法轻松解决第2项问题。您的手机专栏似乎无法100%受到信任,但如果此问题得到解决,则查询(我认为)可能是:
SELECT
o.order_id
, p.pre_sale_phone_manual_id AS id
, p.created
, p.user_id
FROM `order` o
INNER JOIN `customer` c
ON c.customer_id = o.customer_id
INNER JOIN (
SELECT
pspm.pre_sale_phone_manual_id AS id
, pspm.created
, pspm.user_id
, pspm.phone
FROM `pre_sale_phone_manual` pspm
INNER JOIN (
SELECT
phone
, MIN(created) AS created
FROM `pre_sale_phone_manual`
GROUP BY
phone
) dc
ON pspm.created = dc.created
AND pspm.phone = dc.phone
) p
ON c.phone = p.phone /* see notes on this join */
WHERE o.created > p.created
AND o.created < DATE_ADD(p.created, INTERVAL 183 DAY)
AND o.created > '2013-12-30 08:28:37'
查询开发人员可以做的事情不多,除非他们也可以控制表。一种方法是添加可靠的列并索引这些新列。 MySQL没有我所知道的基于函数的索引或计算列,所以你如何获得可靠的数据并不简单。
This previous question包含可能有用的功能,例如,如果您向客户添加good_phone
/*
Function From user1467716
https://stackoverflow.com/questions/287105/mysql-strip-non-numeric-characters-to-compare
*/
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
WHILE iterator < (LENGTH(input) + 1) DO
IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
END IF;
SET iterator = iterator + 1;
END WHILE;
RETURN output;
END
//
update customer
set good_phone = strip_non_digit(InputPhone)
;
//
如果您无法解决不可靠的手机数据,那么您将遭受暗示的性能,而不是&#34; phone = phone&#34;你将需要继续:
AND REPLACE(REPLACE(REPLACE(替换(c.phone,&#34; - &#34;,&#34;&#34;),&#34;。&#34;,&#34; &#34;),&#34; +&#34;,&#34;&#34;),&#34;&#34;,&#34;&#34;)等。
答案 1 :(得分:1)
所以,只是重复别人和我自己已经写过的内容:
CROSS JOIN
执行昂贵的pre_sale_phone_manual_id
。左侧的所有行与右侧的所有行组合。那是一堆行。LEFT JOIN
上有customer
,但由于INNER JOIN
条件(WHERE
条件),您实际上已经在LIKE
。{ / LI>
LIKE
”开头,则%
条件完全无法从索引中受益。 (如果索引足够小以适应PM,它可以在某种程度上受益,因为索引扫描会更快。但它仍然是O(n)而不是O(log(n)))我假设OUTER JOIN
和CROSS JOIN
不是必需的,即你总是在pre_sale_phone_manual_id
中有记录,我做了一个微不足道的,显然未经测试的重写。如果假设有效,你可以尝试一下。
SELECT o.order_id,
p.pre_sale_phone_manual_id AS id,
p.created,
p.user_id
FROM `order` o
JOIN `customer` c ON c.customer_id = o.customer_id,
JOIN `pre_sale_phone_manual` p
LEFT JOIN `pre_sale_phone_manual` p1
ON p.pre_sale_phone_manual_id=p1.pre_sale_phone_manual_id
AND p.created > p1.created
WHERE p1.user_id IS NULL
AND p.phone <> ""
AND REPLACE(REPLACE(REPLACE(REPLACE(c.phone, "-", ""), ".", ""), "+", ""), " ", "")
LIKE CONCAT('%', RIGHT(REPLACE(REPLACE(REPLACE(REPLACE(p.phone, "-", ""), ".", ""), "+", ""), " ", ""), 10))
AND o.created > p.created
AND o.created < (DATE_ADD(p.created, INTERVAL 183 DAY))
AND o.created > '2013-12-30 08:28:37'
因此,由于旧版本中的性能问题,传统上我们更喜欢MySQL中的JOIN
。但是,如果您使用NOT EXISTS (...)
代替LEFT JOIN ... p1
,也可以尝试查看会发生什么。
答案 2 :(得分:1)
首先,你有混合的隐式和显式连接。为了便于阅读,请为pre_sale_phone_manual使用显式的INNER JOIN。这也应该使用ON子句来完成。
此外,您在WHERE子句中引用customer中的列,这似乎使得客户的左连接无关紧要。将其更改为内部联接。
然而,这仍然不会很快。您对pre_sale_phone_manual和订单的加入正在使用DATE_ADD,这将强制对字段进行计算,并可能阻止对该连接使用索引。
同样适用于检查客户和pre_sale_phone_manual表上的电话字段(特别是当您在LIKE上使用前导通配符时)。
每个结果行的pre_sale_phone_manual上有多少条记录?如果数量很大,则可能值得使用子查询来排除除最新查询之外的所有内容。
SELECT o.order_id,
p.pre_sale_phone_manual_id AS id,
p.created,
p.user_id
FROM `order` o
INNER JOIN
(
SELECT pre_sale_phone_manual_id, MAX(created) AS max_created
FROM `pre_sale_phone_manual`
GROUP BY pre_sale_phone_manual_id
) p_sub
ON o.created > p_sub.max_created AND o.created < (DATE_ADD(p_sub.max_created, INTERVAL 183 DAY))
INNER JOIN pre_sale_phone_manual p
ON p.pre_sale_phone_manual_id = p_sub.pre_sale_phone_manual_id
AND p.created = p_sub.max_created
INNER JOIN `customer` c ON c.customer_id = o.customer_id
WHERE p.phone <> ""
AND REPLACE(REPLACE(REPLACE(REPLACE(c.phone, "-", ""), ".", ""), "+", ""), " ", "") LIKE CONCAT('%', RIGHT(REPLACE(REPLACE(REPLACE(REPLACE(p.phone, "-", ""), ".", ""), "+", ""), " ", ""), 10))
AND o.created > '2013-12-30 08:28:37'
答案 3 :(得分:1)
当一个人没有准确的数据时,调整很难。但无论如何......
你在pre_sale_phone_manual
的两侧同一个coloumn上有一个奇怪的自我加入(!?)。这看起来有点像错误。无论如何,Mysql支持analytic functions,我认为您的自联接可以使用这些转换为单个表访问。
其他人已经注意到非规范化电话号码的类似情况会受到影响。我建议如下:在INVERSE_PHONE
和p
上添加一列c
,其中包含电话号码,但会根据您的选择和从后到前的规范进行标准化({{ 3}})。在p
上放置一个索引列,并在where子句中使用它。这基本上取代了基于函数的索引,看起来似乎是maintain it using triggers,但据我所知,它已经消失了。
如果仍然没有做到这一点,请对(DATE_ADD(p.created, INTERVAL 183 DAY))
执行相同的操作,并将p
的所有列放在一个在select中使用的索引中。从最具选择性的专栏开始。
一方面有一个表而另一方有另一个表的所有条件都是连接的一部分,所以将它们放在连接条件而不是where子句中。这有望对性能没有影响,但它使语句更容易阅读。
答案 4 :(得分:0)
我对Oracle更熟悉,但索引怎么样?它们可以大大加快查询速度并避免对表进行全面扫描,尤其是在左外连接处。从explain-output我看到没有使用这样的索引。
尝试放置智能索引。我再次使用Oracle,但我认为mySQL也应该在主键和外键上放置索引。