mysql数据库优化之mysql索引优化与limit分页优化

发布时间:2020-11-15编辑:脚本学堂
本文内容:mysql数据库优化的一些知识点,包括mysql索引优化、mysql limit分页查询优化,并介绍了select中函数的用法与例子等。

mysql/ target=_blank class=infotextkey>mysql数据库中,索引很重要:
state字段为索引。
 

select * from feed_urls where state='ok' and feed_url<>'' limit n,10
 

当记录数量很大时,有几万之后,这句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

2. 当mysql分页很大时
 

复制代码 代码示例:

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左右)。

<a href=http://www.jb200.com/zt/mysql-limit/ target=_blank class=infotextkey>mysql limit</a><a href=http://www.jb200.com/zt/fenyechaxun/ target=_blank class=infotextkey>分页查询</a>优化

膜拜下这burst.net最低档次的vps (30rmb/month)。
 

复制代码 代码示例:
root@xiaoxia-pc:~/# ping feed.readself.com -n
ping app.readself.com (184.82.185.32) 56(84) bytes of data.
64 bytes from 184.82.185.32: icmp_req=1 ttl=45 time=161 ms
64 bytes from 184.82.185.32: icmp_req=2 ttl=45 time=161 ms
64 bytes from 184.82.185.32: icmp_req=3 ttl=45 time=161 ms
 

用同样的方法,优化了搜索引擎的排名算法。即排名过程中选取尽量少的值出来排序,排序后再join一次获取结果的信息。

排序过程:
 

复制代码 代码示例:
select u.*, count_level(u.id) lv
  from(
    select f.id, f.ref_count, match(i.link,i.title) against (keywords) score
    from feed_index i
    join feed_urls f on f.id=i.id
    where match(i.link,i.title) against (keywords)
    order by score*0.5 + score*0.5*(ref_count/max_ref_count_in_result) desc
  limit offset,10
) d join feed_urls u on u.id = d.id
 

目前处理10万记录的全文索引数据,mysql还是可以满足的,就是不知道上百万之后,还能不能撑下去。撑不下去就依赖第三方的工具了,例如sphinx :)

3. select中函数
给feeddb增加了层次的显示。因为本人太懒,所以没有给数据库表增加一个记录深度的字段。所以,直接写了一个mysql的自定义函数 count_level,用来统计通过parent_id一直找到顶层经过的路径长度(level)。
 

复制代码 代码示例:
create definer=`feeddb_rw`@`%` function `count_level`(fid char(40)) returns int(11)
begin
     set @levels = 0;
     set @found = false;
     while not @found do
      select previous_id into @prev_id from feed_urls where id=fid;
      if @prev_id is null or @prev_id = '' then
  set @found = true;
             else
              set @levels = @levels + 1;
              set fid = @prev_id;
      end if;
     end while;
     if @prev_id is null then
         return null;
     end if;
     return  @levels;
end

在网页显示时,使类似以下sql语句
 

复制代码 代码示例:
mysql> select u.*, count_level(u.id) from feed_urls u order by ref_count desc limit 12000,1g
*************************** 1. row ***************************
               id: e42f44b04dabbb9789ccb4709278e881c54c28a3
             link: http://www.jb200.com/
            title: le hamburger et le croissant
      update_time: 2012-05-15 14:50:53
            state: ok
      http_server: gse
         abstract: lepekmezest un épais sirop bordeaux obtenu par réduction dumo?t de raisin, une sorte de mélasse de raisin, en somme. légèrement acidulé, il apporte du pep's aux yaourts et nappe avec bonheur les
      previous_id: 129cabd96e7099a53b78c7ddeff98658351082e9
        ref_count: 9
            error: null
              aid: 174262
count_level(u.id): 8
1 row in set (4.10 sec)
 

4100ms。一定对12000个条目都算了一次count_level,然后再进行排序。

改进方法:
先select limit,再在派生的临时表里,计算count_level。
 

复制代码 代码示例:
mysql> select u.*, count_level(u.id) from (
      select id from feed_urls order by ref_count desc limit 27521,1
 ) d join feed_urls u on u.id=d.idg
*************************** 1. row ***************************
               id: 61df288dda131ffd6125452d20ad0648f38abafd
             link: http://www.jb200.com/
            title: my nokia mobile
      update_time: 2012-05-14 14:06:57
            state: ok
      http_server: apache/2.2.19 (unix) mod_ssl/2.2.19 openssl/1.0.0-fips mod_auth_passthrough/2.1 mod_bwlimited/1.4 frontpage/5.0.2.2635
         abstract: archivesselect monthmay 2012april 2012march 2012february 2012january 2012december 2011november 2011october 2011september 2011august 2011july 2011june 2011may 2011april 2011march 2011february 2011janua
      previous_id: f37af92bb89c08f6d4b69e72eab05d8ab1e2aca4
        ref_count: 5
            error: null
              aid: 154996
count_level(u.id): 8
1 row in set (0.09 sec)
 

还可以继续优化,例如建立一个字段存储level的值等。