我查询了一个我很久以前建立的聊天网站,由于流量很大,我糟糕的查询设计已经赶上了我。这里有一个来自我的长查询日志的例子:
SELECT DISTINCT user.id
FROM user
STRAIGHT_JOIN user_pics
ON user.id=user_pics.uid
STRAIGHT_JOIN user_account
ON user_account.user_id=user.id
WHERE registered = 1 AND
user.id<>0 AND
user.id<>23847 AND
user.id<>12392... (IT HAS LITERALLY 1000 OF THESE)
AND user_pics.main=1 AND
user_pics.approved=1 AND
user_pics.deleted<>1 AND
gender LIKE '%female%' AND
country LIKE '%United Kingdom%' AND
city LIKE '%birmingham%' AND
sexorientation LIKE '%Straight%'
ORDER BY updatedate DESC
LIMIT 20;
查询大约需要15秒才能执行,我也已经索引了所有引用列。将查找到临时表替换1000“AND user.id&lt;&gt; 0”标记会改进查询。我想我会先去询问并做出改变。如果您可以通过代码推荐任何有用的更改,我将非常感激。
编辑:“user.id&lt;&gt; 23847”标记是在php中通过简单的select创建的,然后是foreach数组循环,将它们添加到更大的sql查询中。
编辑2:感谢您的帮助,通过使用“不在”,他们的查询从13秒减少到0.3秒。
答案 0 :(得分:5)
尝试使用EXPLAIN
http://dev.mysql.com/doc/refman/5.0/en/explain.html
你会发现什么可以更好。
这可以替换为user.id NOT IN(23847 ,0 , 23847 ,...)
使用NOT IN()
答案 1 :(得分:1)
是的,如果您要使用,
user.id NOT IN (SELECT id FROM idExemptTable)
这比单独检查每个id
要快得多我已经包含了另一个答案的链接,该答案进一步详细介绍了IN声明: -
SQL: SELECT IN faster and best practice?
我也不确定你为什么要使用通配符匹配和LIKE来检查国家名称。
答案 2 :(得分:1)
不使用慢速not-equals-to,而是使用技巧:选择那些不被选中的技巧,用LEFT JOIN
将其连接回原始表,并通过过滤获得其余的:< / p>
SELECT DISTINCT user.id
FROM user
STRAIGHT_JOIN user_pics ON user.id=user_pics.uid
STRAIGHT_JOIN user_account ON user_account.user_id=user.id
LEFT OUTER JOIN
(SELECT u.id from user u where u.id in (0,23847, 12397 ... ... ...)) as notToBeIncluded ON user.id=notToBeIncluded.id -- the users that are to be excluded
WHERE registered=1
AND notToBeIncluded.id IS NULL --this is the important part.
AND user_pics.main=1
AND user_pics.approved=1
AND user_pics.deleted<>1
AND gender LIKE '%female%'
AND country LIKE '%United Kingdom%'
AND city LIKE '%birmingham%'
AND sexorientation LIKE '%Straight%'
ORDER BY updatedate DESC LIMIT 20;
编辑我是多么愚蠢......你甚至提到过,你所拥有的不需要的ID是另一个查询的结果!在这种情况下,不会从PHP中获得结果,直接在查询中使用它!这将使它更快。
所以:
(这里需要一个字符串用于标记以正确格式化)
SELECT DISTINCT user.id
FROM user
STRAIGHT_JOIN user_pics ON user.id=user_pics.uid
STRAIGHT_JOIN user_account ON user_account.user_id=user.id
LEFT OUTER JOIN
(SELECT u.id from user <[ your other query here ]> ) as notToBeIncluded ON user.id=notToBeIncluded.id -- the users that are to be excluded
WHERE registered=1
AND notToBeIncluded.id IS NULL --this is the important part.
--( conditions removed for brewity)
ORDER BY updatedate DESC LIMIT 20;
其他建议:
答案 3 :(得分:0)
向users表添加一个额外字段并为其编制索引。在
中为每个用户设置值为1user.id<>0 AND
user.id<>23847 AND
user.id<>12392...
,每个其他用户都为0。
然后在上面的查询中按此字段进行过滤。
答案 4 :(得分:0)
ppeterkas解决方案的微小变化,假设使用临时表与其中不需要的用户。同样将LIKE更改为直线等于(甚至更好地使用标志,甚至是位串,具体取决于字段是否存储值的组合)。
SELECT DISTINCT user.id
FROM user
STRAIGHT_JOIN user_pics ON user.id=user_pics.uid
STRAIGHT_JOIN user_account ON user_account.user_id=user.id
LEFT OUTER JOIN tmp_users_to_ignore ON user.id = tmp_users_to_ignore.id
WHERE registered = 1
AND tmp_users_to_ignore.id IS NULL
AND user_pics.main=1
AND user_pics.approved=1
AND user_pics.deleted<>1
AND gender = 'female'
AND country = 'United Kingdom'
AND city = 'birmingham'
AND sexorientation = 'Straight'
ORDER BY updatedate DESC
LIMIT 20;
答案 5 :(得分:0)
我明白了:
编辑:“user.id&lt;&gt; 23847”标记是在php中通过简单的select创建的,然后是foreach数组循环,将它们添加到更大的sql查询中。
那么为什么不创建子查询?
我们假设您的第一个查询是SELECT * FROM user WHERE yourcondition
。
您需要所有数据返回吗?如果不是,请在第二个查询中执行此操作:
SELECT DISTINCT user.id
FROM user
STRAIGHT_JOIN user_pics ON user.id=user_pics.uid
STRAIGHT_JOIN user_account ON user_account.user_id=user.id
WHERE registered=1
AND user.id NOT IN(SELECT id FROM user WHERE yourcondition)
AND user_pics.main=1
AND user_pics.approved=1
AND user_pics.deleted<>1
AND gender ='female'
AND country LIKE '%United Kingdom%'
AND CITY LIKE'%birmingham%' AND sexorientation LIKE'%Straight%' ORDER BY更新了DESC LIMIT 20;