高性能MySQL学习笔记(二)

 前缀索引

(1)如果以多个字段作为索引(a,b,c)作为一个key,在查询时分根据匹配情况是否使用索引,所以,a/ a,b/ a,b,c这样的查询都能使用到,但是b,a/a,c/c,a等不符知前缀匹配就不能使用,所以在写sql时需要注意
(2)对于很长的字符串需要生成key时,可以找出最常使用的字符串的最常见前缀(前N个字条作为key,而不是将全个字符串作为key),这样一方面牺牲少少的精确性(有多余数据,再过滤),另一方面大大提高了检索效率,同时减少了索引的维护开销(存储空间,更新等)

如有200w的url,统计结果如下:

select count(*) as cnt, url from iUrl group by url order by cnt desc limit 10;
+-----+-------------------+
| cnt | url               |
+-----+-------------------+
| 380 | http://www.f.com, |
| 354 | http://www.r.com, |
| 340 | http://www.h.com, |
| 336 | http://www.u.com, |
| 336 | http://www.e.com, |
| 336 | http://www.i.com, |
| 332 | http://www.k.com, |
| 330 | http://www.t.com, |
| 330 | http://www.w.com, |
| 326 | http://www.l.com, |
+-----+-------------------+
10 rows in set (3.04 sec)

取前N个字符作为前缀,使得匹配效果与全统计接近为止(这里由于都是以http://www. 开头,所以看不出效果)
select count(*) as cnt, left(url,11) from iUrl group by url order by cnt desc limit 10;  --left(url,11) 取字符串的左N个
也可以通过计算全列选择性,详细方法要细看书了

添加前缀索引

alter table iUrl add key ( url(11) );

前缀索引好处是:1.减少索引大小 2.提高速度,但也有不足:不能用于order by/group by, 不能作为覆盖索引

聚集索引

不是一种单独的索引,而是一种存储方式。InnoDB支持,保存了B-Tree和数据行,它将数据行保存在索引的叶子页中。每个表只有一个聚集索引。InnoDB按主键进行聚集。

聚集索引优点:
1.可能把相关数据保存在一起
2.数据访问快
3.结全覆盖索引查询可以使用包含叶子节点中的主键值

缺点:
1.聚集能最大限度提升I/O密集负载的性能,但如果数据能装入内存,顺序就无所谓了,这样聚集就没有什么用处了
2.插入速度严重依赖于插入顺序(按主键顺序插入是最快的)
3.更新聚集索引开销大
4.插入、更新时可能会发生分页,导致更多的磁盘空间
5.聚集表扫描比全表慢

覆盖索引

索引叶子节点包含了它们索引的数据,能满足查询需要的数据,不用再去读取所在行。

优势:
1.索引记录通常远小于全行大小,如果只读取索引,就能极大地减少数据访问量(MyISAM还会对索引压缩,也方便加载到内存)
2.索引是按索引值排序的,因此I/O密集型范围访问将会比随机地从磁盘上提取每一行数据快得多
3.大部分存储引擎缓存索引比缓存数据更好
4.对InnoDB特别有用,因为它使用聚集索引

注意:

1.如果选择了所有列,就不能用到覆盖索引了
2.不能执行LIKE操作

可能通过两步查询,合并索引,延伸覆盖

select * from products 
    JOIN (
        select prod_id from products  where actor = 'kejie' AND title LIKE '%ABC%'    --第一阶段会使用到覆盖索引
    ) AS t1 ON ( t1.prod_id = products.prod_id );

不过如果结果集太大或者太少,优化也不会明显,或者不如直接查询,所以需要按实际业务查询对比,再确定是否需要这样优化!

注意导致不能使用索引的排序查询:

1. 两种不同的排序方向
2.引用了一个不在索引中的列
3. 不能形成索引的最左前缀
4.第一列上有范围条件

...where rental_date > '2005-05-25'

压缩索引

MyISAM使用前缀压缩以减少索引大小,运行更多索引被装入内存,以在某些情况下极大地提高性能。

但也有缺点:压缩后索引查找对于CPU密集的负载会慢几倍,反向扫描甚至更慢

多余和重复索引

CREATE TABLE test (
    ID INT NOT NULL PRIMARY KEY,
    UNIQUE( ID ),
    INDEX( ID)
);

MySQL利用索引实现了UNIQUE约束和PRIMARY KEY约束,因此这样是在同一列上创建了三个索引!

更多的索引的表中插入新行慢得多,添加新索引会能insert/update/delete有较大的性能影响,特别新索引遇到内存限制时

1.对于数据服务器,要考虑有足够大的内存
2.内存了的估计可以参考数据索引的大小(尽可能将索引都装入内存)

索引策略总结

1.检查最常运行的查询
2.避免不知道什么查询会使用索引之前就创建这种错误
3.对索引形成一个优化配置
    1) 检查响应时间
     
2)任何地方都要试着扩展索引,而不是新增索引

高性能查询的常用技巧

1.使用额外的索引
2.冗余字段
3.通过缓存表,汇总表加速读取

高性能查询优化

1.优化数据访问
    
1) 减少向服务器请求不需要的数据 ->可以避免给服务器造成额外的负担, 网络开销, 内存消耗,CPU资源
    2) 使用limit限制返回记录数
    3) 对select *保持怀疑态度,是否真的需要 -> 会造成覆盖索引失效

2.MySQL检查
    1) 执行时间
    2) 检查的行数
    3) 返回的行数 -> 检查explain的type: 全表扫描(Full Table Scan), 索引扫描(Index Scan),范围扫描(Range Scan), 唯一索引查找(Unique Index Lookup), 常量(Constant)
--> 三种方式都会写入慢速日志,所以浏览该日志是检索查找了过多数据的最佳方式
慢速查询日志
要启用慢速查询日志需要在 my.cnf 中所做的配置, 也可以用--log-slow-queries[=file_name]选项启动时,mysqld写一个包含所有执行时间超过long_query_time秒的SQL语句的日志文件。
清单 1. 启用 MySQL 慢速查询日志
            

[mysqld]
; enable the slow query log, default 10 seconds
log-slow-queries
; log queries taking longer than 5 seconds
long_query_time = 5
; log queries that don't use indexes even if they take less than long_query_time
; MySQL 4.1 and newer only
log-queries-not-using-indexes
如果数据量很大,而生成的结果中数据行很少,可以尝试更复杂的修改:
1) 使用覆盖索引, 它存储了部分列数据,不用去读取完整的行
2) 更改架构
3) 重写复杂的查询, 使得优化器可以优化执行
重构查询方式
1.将复杂的查询分解成多个简单的查询 -> 注意不是说将一个读取10行的查询分成10次读取
2.缩短查询,每次执行一小部分,减少受影响的行数
3.分解联接
    select * from tag,
            join tag_post on tag_post.tab_id = tag.id
            join post on tag_post.post_id = post.id
    where tag.tag = 'mysql';
可以用以下面的语句代替:
    select * from tag where tag = 'mysql';
    select * from tag_post where tag_id = 1234;
    select * from post where post.id in ( 123, 456, 9098,8904 );
这样的分解有以下重大优势:
1)缓存效率更高
2)每个表一个查询可以更用效地利用表锁, 不会长时间内锁住表
3)在应用程序端进行联接可以更方便地扩展数据库
4)可以减少多余行的访问
5)查询更高效,使用了IN使得MySQL可以对ID进行排序,更高效地取得数据
小结:什么时候在应用程序端进行联接效率更高?
1)可以缓存早期查询的大量数据
2)使用了更多的MyISAM表
3)数据分布在不同服务器上
4)对于大表使用IN代替了联接
5)一个联接引用了同一个表多次
关联子查询
 1. MySQL对子查询的优化很差,最差的是在where子句中使用了IN              
     select * from sakila.film where film_id in (
        select file_id from sakila.film_actor where actor_id = 1 );
mysql> explain select * from sakila.film where film_id in ( select film_id from sakila.film_actor where actor_id = 1 );                                            
+----+--------------------+------------+--------+------------------------+---------+---------+------------+------+-------------+
| id | select_type        | table      | type   | possible_keys          | key     | key_len | ref        | rows | Extra       |
+----+--------------------+------------+--------+------------------------+---------+---------+------------+------+-------------+
|  1 | PRIMARY            | film       | ALL    | NULL                   | NULL    | NULL    | NULL       | 1000 | Using where |
|  2 | DEPENDENT SUBQUERY | film_actor | eq_ref | PRIMARY,idx_fk_film_id | PRIMARY | 4       | const,func |    1 | Using index |
+----+--------------------+------------+--------+------------------------+---------+---------+------------+------+-------------+


也可改用exists
     select * from sakila.film where exists (
        select file_id from sakila.film_actor where actor_id = 1
        and film_actor.film_id = film.film_id
);
explain select * from sakila.film where exists ( select film_id from sakila.film_actor where actor_id = 1 and film_actor.film_id = film.film_id );          
+----+--------------------+------------+--------+------------------------+---------+---------+---------------------------+------+-------------+
| id | select_type        | table      | type   | possible_keys          | key     | key_len | ref                       | rows | Extra       |
+----+--------------------+------------+--------+------------------------+---------+---------+---------------------------+------+-------------+
|  1 | PRIMARY            | film       | ALL    | NULL                   | NULL    | NULL    | NULL                      | 1000 | Using where |
|  2 | DEPENDENT SUBQUERY | film_actor | eq_ref | PRIMARY,idx_fk_film_id | PRIMARY | 4       | const,sakila.film.film_id |    1 | Using index |
+----+--------------------+------------+--------+------------------------+---------+---------+---------------------------+------+-------------+
改写成以联接方式:
    select film.* from sakila.film
        inner join sakila.film_actor using( film_id )
    where actor_id = 1;
mysql> explain select film.* from sakila.film inner join sakila.film_actor using( film_id ) where actor_id = 1; 
+----+-------------+------------+--------+------------------------+---------+---------+---------------------------+------+-------------+
| id | select_type | table      | type   | possible_keys          | key     | key_len | ref                       | rows | Extra       |
+----+-------------+------------+--------+------------------------+---------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | film_actor | ref    | PRIMARY,idx_fk_film_id | PRIMARY | 2       | const                     |   19 | Using index |
|  1 | SIMPLE      | film       | eq_ref | PRIMARY                | PRIMARY | 2       | sakila.film_actor.film_id |    1 |             |
+----+-------------+------------+--------+------------------------+---------+---------+---------------------------+------+-------------+
可以看出,使用IN/EXISTS子查询效果是一样的,而且性能方面,都是扫描了1000+1行,而使用内联接,只扫描了19 + 1 行!!
使用mysqlslap测试,在100并发查询下子查询方式用时0.448s, 联接方式只用了0.285, 速度提升近一倍.
但也不是绝对的,所以最好使用测试后数据说明问题, 而不是绝对的!
同时对一个表进行select / update
MySQL不可以同时对一个表进行select/update操作,如:
update tbl as outer_tbl
    set cnt = (
           select count(*) from tbl as inner_tbl
            where inner_tbl.type = outer_tbl.type
);
一种变能的方法是使用临时表:
update tbl as outer_tbl
    inner join (

           select type, count(*) as cnt from tbl

           group by type
    ) as der using( type )
set tbl.cnt = der.cnt;
优化特定查询
1.count 
    1)只会统计表达式有值的次数!, 所以要统计所有行数,要使用count(*), 而不要使且count(列)
    2)如果要统计的量很大,可以试着反过来,统计总数 - 不需要部分
        select count(*) from world.city where id > 5;
        可以改写为:
        select ( select count(*) from world.city) - count(*) from world.city where id <= 5;
2.优化联接
    1) 确保ON或USING使用的列上有索引
    2)确保group by或order by只使用一个表的列, 这样MySQL才会对这些操作使用到索引
3.优化子查询
    1)
尽可能使用联接!!
    2)子查询创建的临时表是没有索引的
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值