在mysql/ target=_blank class=infotextkey>mysql数据库中,索引很重要:
state字段为索引。
当记录数量很大时,有几万之后,这句sql就很慢了。
因为feed_url没有建立索引。
解决方法:
把feed_url为空的,设为一个ok以外的state值,就行了。
1、索引不是万能的
为了计算记录总数,以下语句会很慢。
mysql> select count(*) from feed_urls where state='error';
+----------+
| count(*) |
+----------+
| 30715 |
+----------+
1 row in set (0.14 sec)
mysql> explain select count(*) from feed_urls where state='error'g
*************************** 1. row ***************************
id: 1
select_type: simple
table: feed_urls
type: ref
possible_keys: state,page_index
key: page_index
key_len: 10
ref: const
rows: 25936
extra: using where; using index
1 row in set (0.00 sec)
state为索引,请求用时140ms。遍历了state='error'索引下的每一条记录。
mysql> select state,count(*) from feed_urls group by state;
+----------+----------+
| state | count(*) |
+----------+----------+
| error | 30717 |
| fetching | 8 |
| nofeed | 76461 |
| ok | 74703 |
| queued | 249681 |
+----------+----------+
5 rows in set (0.55 sec)
mysql> explain select state,count(*) from feed_urls group by stateg
*************************** 1. row ***************************
id: 1
select_type: simple
table: feed_urls
type: index
possible_keys: null
key: state
key_len: 10
ref: null
rows: 431618
extra: using index
1 row in set (0.00 sec)
请求用时550ms。遍历了每个state下的每一条记录。
改进方法:
独立一个表用来计数,使用mysql的trigger同步计数:
create trigger my_trigger after update on feed_urls
for each row begin
if old.state <> new.state then
if new.state='ok' then
update feed_stat set count_feed = count_feed + 1;
end if;
if new.state in ('ok', 'error', 'nofeed') then
update feed_stat set count_access = count_access + 1;
end if;
end if;
end
mysql> select * from feed_urls limit 230000, 1g
*************************** 1. row ***************************
id: 736841f82abb0bc87ccfec7c0fdbd09c30b5a24d
link: http://mappemunde.typepad.com/
title: tim peterson
feed_url: null
update_time: 2012-05-12 11:01:56
state: queued
http_server: null
abstract: null
previous_id: ceea30e0ba609b69198c53ce71c44070d69038c5
ref_count: 1
error: null
aid: 230001
1 row in set (0.50 sec)
mysql> explain select * from feed_urls limit 230000, 1g
*************************** 1. row ***************************
id: 1
select_type: simple
table: feed_urls
type: all
possible_keys: null
key: null
key_len: null
ref: null
rows: 431751
extra:
1 row in set (0.00 sec)
读取一条记录,耗时500ms,因为表记录是变长的,所以mysql不能算出目标位置,只能每一条记录的数过去。
改进方法:
通过索引定位,数索引比数记录要快,因为索引占用的空间比整条记录小很多。
mysql> select * from (select aid from feed_urls order by aid limit 215000, 1) d join feed_urls u on d.aid=u.aidg
*************************** 1. row ***************************
aid: 215001
id: 2e4b1a385c8aae40b3ec2af9153805ca446f2029
link: http://ncse.com/
title: ncse
feed_url: null
update_time: 2012-05-12 10:47:15
state: queued
http_server: null
abstract: null
previous_id: 819a6e3c5edc1624a9b8f171d8d3ae269843785f
ref_count: 3
error: null
aid: 215001
1 row in set (0.06 sec)
mysql> explain select * from (select aid from feed_urls order by aid limit 215000, 1) d join feed_urls u on d.aid=u.aidg
*************************** 1. row ***************************
id: 1
select_type: primary
table:type: system
possible_keys: null
key: null
key_len: null
ref: null
rows: 1
extra:
*************************** 2. row ***************************
id: 1
select_type: primary
table: u
type: const
possible_keys: aid
key: aid
key_len: 4
ref: const
rows: 1
extra:
*************************** 3. row ***************************
id: 2
select_type: derived
table: feed_urls
type: index
possible_keys: null
key: aid
key_len: 4
ref: null
rows: 211001
extra: using index
3 rows in set (0.15 sec)
耗时60ms,比之前的方法快了将近10倍。如果limit语句里还有where a=1,应该建立一个(a,aid)的索引。
话说,mysql好像还是不能直接算出第21500条索引的位置呀,这种方法还是数了索引了,能算出来就直接0ms了。不过这样的效率,对于百万级的,还能应付吧。如果是千万级的或者像我之前在ks创建的一张上亿条记录的表(120g),这种方法就肯定不行了。
经过上述优化,打开最后一页的速度已经很快了(之前需要800ms,现在则为300ms左右)。
膜拜下这burst.net最低档次的vps (30rmb/month)。
用同样的方法,优化了搜索引擎的排名算法。即排名过程中选取尽量少的值出来排序,排序后再join一次获取结果的信息。
排序过程:
目前处理10万记录的全文索引数据,mysql还是可以满足的,就是不知道上百万之后,还能不能撑下去。撑不下去就依赖第三方的工具了,例如sphinx :)
3. select中函数
给feeddb增加了层次的显示。因为本人太懒,所以没有给数据库表增加一个记录深度的字段。所以,直接写了一个mysql的自定义函数 count_level,用来统计通过parent_id一直找到顶层经过的路径长度(level)。
在网页显示时,使类似以下sql语句。
4100ms。一定对12000个条目都算了一次count_level,然后再进行排序。
改进方法:
先select limit,再在派生的临时表里,计算count_level。
还可以继续优化,例如建立一个字段存储level的值等。