我正在将分页添加到我的“最新消息”php 脚本中,但遇到了一个问题。
SELECT sid, title, time, bodytext, author, url FROM news WHERE approved=1 ORDER BY time desc LIMIT $start, $limit
Run Code Online (Sandbox Code Playgroud)
我在批准的时间列上有索引。
问题是当我对查询执行 EXPLAIN 时,它显示它正在使用“已批准”索引 - 但它仍然必须扫描所有 1000 行才能获得我想要显示的 10 个新条目。
无论如何要优化查询,以免发生这种情况?
此时,您必须在三列上创建索引
ALTER TABLE news ADD INDEX authored_time_sid_ndx (authored,time,sid);
Run Code Online (Sandbox Code Playgroud)
然后,您必须稍微重构查询以减少 MySQL 查询优化器预期处理的两件事:
这是您的原始查询:
SELECT sid, title, time, bodytext, author, url FROM news WHERE approved=1 ORDER BY time desc LIMIT $start, $limit
Run Code Online (Sandbox Code Playgroud)
您可以做的是先收集所有密钥:
SELECT sid FROM news WHERE approved=1 ORDER BY time desc LIMIT $start, $limit
Run Code Online (Sandbox Code Playgroud)
这将执行全索引扫描而不是全表扫描,因为所有三列都在索引中。生成临时表的负担要少得多,因为您只扫描较小资源(索引)中的 3 列,而不是来自较大资源(表)的 6 列,并且不粘贴 url 和正文(可能是 VARCHAR(300) 和 TEXT 字段)只是为了收集密钥。
接下来,将 SELECT sid 查询放入一个内联表中,并仅使用获取的键将它们连接回新闻表。示例:假设您的LIMIT 变量为 200,10。这意味着您希望移动到新闻表的第 201 行并从该点获得 10 个键。这意味着无论您在哪个页面上,您都希望一次收集 10 个密钥,并且一次只能收集 10 个密钥。
这是新的和改进的查询:
SELECT A.sid, A.title, A.time, A.bodytext, A.author, A.url
FROM
news A INNER JOIN
(SELECT sid FROM news WHERE approved=1 ORDER BY time desc LIMIT $start, $limit) B
USING (sid);
Run Code Online (Sandbox Code Playgroud)
这里唯一真正的成本是对键进行全索引扫描而不是全表扫描。好处是,一旦您从内联 SELECT 中获得 10 个键,那么只会检索 10 个标题、正文、作者和网址。
试一试 !!!
警告
根据经验,只要您索引基数非常低的列(即男性/女性 (2)、单身/已婚/离婚/丧偶 (4)、真/假 (2)、0/1 (2) ) 并且任何一个值的行数超过表中总行数的 5%,MySQL 查询优化器将排除任何和所有索引,您将执行全表扫描或选择错误索引,您最终使用全索引扫描。这就是为什么您必须找到正确的列分布,或者至少执行索引扫描而不是表扫描的原因。
更新 2011-08-08 11:13 EDT
我在想什么?这是我最初推荐的索引:
ALTER TABLE news ADD INDEX authored_time_sid_ndx (authored,time,sid);
Run Code Online (Sandbox Code Playgroud)
该领域应该被批准而不是创作。这是应该的:
ALTER TABLE news ADD INDEX approved_time_sid_ndx (approved,time,sid);
Run Code Online (Sandbox Code Playgroud)
您给出的原始查询是
SELECT sid, title, time, bodytext, author, url FROM news WHERE approved=1 ORDER BY time desc LIMIT $start, $limit
Run Code Online (Sandbox Code Playgroud)
我提议的索引 (approved_time_sid_ndx) 将包括已批准、时间和 sid。
您刚刚提交的答案有以下查询:
SELECT sid, title, time, bodytext, author, url FROM news WHERE approved = 1 AND sid > 0 ORDER BY sid desc LIMIT 500, 10;
Run Code Online (Sandbox Code Playgroud)
既然如此,你现在需要的索引应该是这样的:
ALTER TABLE news ADD INDEX approved_sid_ndx (approved,sid);
Run Code Online (Sandbox Code Playgroud)
批准和 sid 应该在一起。如果它们不在一起,MySQL 查询优化器可能会决定执行主键和索引的内部索引合并,其中批准是第一(或唯一)列。事实上,你的新查询应该重构如下:
SELECT A.sid, A.title, A.time, A.bodytext, A.author, A.url
FROM
news A INNER JOIN
(SELECT sid FROM news WHERE approved=1 and sid > 0 ORDER BY sid desc LIMIT 500,10) B
USING (sid);
Run Code Online (Sandbox Code Playgroud)
请记住,您希望索引包含尽可能多的嵌入在 WHERE 和 ORDER BY 子句中的 DB 列。
值为 1/0 的列上的索引不会很好地建立索引。这是因为很快超过 35% 的索引值将具有相同的值,并且查询优化器知道进行表扫描会更有效。
优化这一点的方法是存储一个分布良好的值。当然,您的 90% 以及越来越多的参赛作品将被“批准” - 它很可能被“发送”或类似的。
存储一个主键(假设 sid)的模数,或者存储最后 3 个或列值的 md5 字符。然后测试 != 0 或 ='000'。您的保存例程需要考虑到模数或散列很可能是 0 或“000”,因此您需要将该值伪造为其他值。