简单(使用整形存储IP,整形确定比字符串类型操做更快)
用UNSINGED INT存储IP地址占用4字节,CHAR(15)则占用15字节。另外,计算机处理整数类型比字符串类型快。使用INT UNSIGNED而不是CHAR(15)来存储IPV4地址,经过MySQL函数inet_ntoa和inet_aton来进行转化。IPv6地址目前没有转化函数,须要使用DECIMAL或两个BIGINT来存储。mysql
避免NULL;
可为null的列会使用更多的存储空间,也须要特殊的处理。当可为null的列别索引的时候,每一个索引记录须要一个额外的字节。在MYISAM引擎中,可能会致使固定大小的索引(如只有一个整数列的索引)变成可变大小的索引。
在数据库优化中,将null改成not null,带来的性能提高并非很大,可是若是该列上有索引,那么须要避免设计为null.
可是也有例外,InnoDB使用单独的bit存储Null值,因此对稀疏数据有很好的空间效率,不一样于MYISAM。sql
范式与反范式
范式的优势就是方便更新,只须要修改少许的信息。缺点就是须要关联,代价很高,会使一些索引策略失效。
反范式将有些数据合并,提升查询的效率,不用由于关联查询致使巨大的性能问题。
汇总表的设计思想:就是将一些统计,如count的数据汇总到一张表中,好比汇总每个月的统计信息,那么若是须要计算季度的信息能够将月的汇总信息加起来。
缓存表的使用场景正好相反,是为了处理实时统计的表,将表中的部分列拿出来做为缓存表,若是主表使用的是innoDB引擎,那么用MYISAM做为缓存表的引擎将会获得更小的索引占用空间,而且支持fulltext索引。myisam引擎使用前缀压缩来减小索引的存储空间,而inoDB不会。
很重要的要提到的是,当咱们在重建汇总表和缓存表的时候,为了保证以前汇总表或者缓存表的可用性,须要创建新表new,而后将new表命名为原来的表,将原来的表命名为old表.这样当新表数据有问题的时候,方便回滚。数据库
建立好的索引对应用来讲特别重要,由于随机IO访问特别慢,若是服务器从存储中读取一个数据块,就为了其中的一行数据那么,那么访问会超级慢。使用索引引用访问的行,那么效率将会提高不少。若是数据存放在机械硬盘,那么速度会更慢。
索引大大减小了服务器须要扫描的数据量,而且可以避免临时表,索引可以将随机IO变为顺序IO。MYSQL支持不少索引,可是最核心的是Btree索引。索引的本质就是数据结构。
我在网络上看到过不少描述B-tree的博客,可是最终仍是以为《mysql高性能》描述的比较靠谱和清楚。以下所示:
缓存
如上图记录的是mysql innodb引擎的聚簇索引,innodb的聚簇索引的叶子节点包含了行的所有数据,而节点页只包括了索引列。聚簇索引表明了数据存储的方式,那么一张表只能有一个聚簇索引,其主键做为聚簇索引的索引列。就是按照主键ID来汇集数据。聚簇索引最大限度的提升了IO密集型应用的性能,至于什么是IO密集,和CPU密集,自行百度。可是其插入顺序会被很大的限制,这就是为何随机的字符串做为主键很差的缘由,一样更新聚簇索引索引列,也就是主键的代价会特别高,由于每一个行都会所以而移动位置。当行的主键要求必须插入到某个已满的页中时,会致使页分裂的问题。
另一个最重要的问题就是:innodb的二级索引,其叶子节点会存储引用行的主键列,这就是覆盖因此为何那么那么重要的缘由。
Mysql索引广泛使用的B+tree,如上图所示,内部节点不存储data,只存储key,而将数据存储在叶子节点上。咱们看到的叶子节点之间的指针是为了方便顺序查找。服务器
接下来咱们要区分MYISAM索引与innodb索引的区别:
MyISAM引擎使用B+Tree做为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:网络
这里设表一共有三列,假设咱们以Col1为主键,则图8是一个MyISAM表的主索引(Primary key)示意。能够看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和二级索引(Secondary key)在结构上没有任何区别,只是主索引要求key是惟一的,而辅助索引的key能够重复。
虽然InnoDB也使用B+Tree做为索引结构,但具体实现方式却与MyISAM大相径庭。
第一个重大区别是InnoDB的数据文件自己就是索引文件。MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件自己就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,所以InnoDB表数据文件自己就是主索引。数据结构
由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。
第二个与MyISAM索引的不一样是InnoDB的二级索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的全部二级索引都引用主键做为data域。并发
如何选择合适的索引顺序:
将选择性比较的高的列放到索引的最前面。(经验法则,可是并非任何状况下都应该这样),计算方式以下所示:
函数
应该将where中的列,设置为索引,而且这个列要有很高的选择性,好比sex这样的列就不该该创建为索引列。并非说,只须要将where中的列创建为索引,覆盖索引可以很好的说明这个问题,并且覆盖索引特别重要,特别重要,特别重要(重要的事情说三遍)工具
覆盖索引也是二级索引,因此在innodb引擎中二级索引保存了行的主键值,因此二级索引确定可以覆盖主键。这个特性很是重要。在覆盖索引中访问数据对IO密集型的应用特别有帮助,由于索引更容易存放在内存中,尤为是MYISAM引擎,采用了前缀压缩的技术更加节省空间,可是压缩以后,会影响查找的性能。Innodb不会压缩。
覆盖索引,有一个特别重要的使用技巧就是延迟关联。以下:
在这里没法使用覆盖索引,由于没有一个索引可以覆盖*,MYSQL不能再索引中使用Like”%%”,能够执行like”..%”;采用延迟关联的策略以下所示:
如何使用覆盖索引进行排序,避免临时表
只有当索引列的顺序和order by的子句顺序彻底一致,而且全部的列的排序方向一致,才会使用覆盖索引。若是有关联,那么order by中的列所有为第一个表才能使用索引。
最重要的一点是:必须知足最左前缀的要求。若是在where或者Join,子句中指定了列的常量,也可以知足最左前缀
好比索引列为(col1,col2,col3),在where中co1=”1”,order by col2,是知足最左前缀的。
MYISAM的前缀压缩索引,没法使用二分查找,只能从头开始扫描。正序的速度还能够,倒序就不好了。(由于其压缩依赖前面的行)
咱们老是使mysql可以使用更多的索引列:
在这里有一个技巧就是使用In,使用in使其可以知足最左前缀的要求,直到遇到第一个范围查询,好比索引(sex,age),那么where sex in(1,0) and age
<20;为何将age放到后面就是由于age多半是范围查询。可是In里面的列表太大,会一样带来性能问题,因此该方法不能滥用。
这跟前面说的选择性高的放到列首是冲突的,可是咱们必须认识到sex是常常用到的字段,放到索引里面是理所固然的,那么其余范围比较大的列,如age应该放到其后面。从而可以使用In技巧来知足最左前缀。
避免多个范围查询:
mysql只能使用一个范围查询,其后面的范围查询将没法使用索引。
关于索引的使用,以前我在阿里云数据库社区找到的那个帖子,上面的三个问题都超级经典,我以为能够很好的说明问题。列举以下:
最后一个问题,like”%%”模糊查询是不会使用索引的,我以为应该是做者写错了。
Q1: SELECT DISTINCT
LoginId,SubId FROM TB WHERE 1 ORDER BY Visit ASC LIMIT 8888, 10
排序方式有多种,Visit 、ID、Count等,是否是有一种排序就要建一个“覆盖索引”? 还须要单列给Visit 、ID、Count、SubId、LoginId建索引吗?
A1(俞月):这个SQL等价于: select LoginId, SubId from TB where 1 group by LoginId, SubId ORDER BY Visit ASC LIMIT 8888, 10
单纯就这条SQL而言,ORDER BY Visit ASC 是不必的,由于select选出的字段中没有Visit字段,建议添加组合索引(LoginId,SubId)。
假如SQL是 select LoginId,SubId,Visit from TB where 1 group by LoginId, SubId ORDER BY Visit ASC LIMIT 8888, 10
这里能够建议组合索引(LoginId,SubId) 或者(LoginId,SubId,Visit)
将Visit字段添加到索引中,仍旧避免不了排序,上面SQL的执行过程是:
tb
(id
int(11) DEFAULT NULL,LoginId
int(11) DEFAULT NULL,SubId
int(11) DEFAULT NULL,vist
int(11) DEFAULT NULL,idx1
(id
),login_subId
(LoginId
,SubId
),login_subId_vist
(LoginId
,SubId
,vist
)Q2(cyb): 关于分页 limit 134557, 10优化,网上能找到的资料都是用WHERE id >或 id< 取一些范围定位,可是不实用啊,例如评分1-5分排序分页就不能用了
A2(俞月):普通limit M,N的翻页写法,每每在越日后翻页的过程当中速度越慢,缘由 mysql会读取表中的前M+N条数据,M越大,性能就越差:
select * from t where sellerid=100 limit 100000,20 优化写法:
select t1.* from t t1, (select id from t sellerid=100 limit 100000,20) t2 where t1.id=t2.id;
优化后的翻页写法,先查询翻页中须要的N条数据的主键id,在根据主键id回表查询所须要的N条数据,此过程当中查询N条数据的主键ID在索引中完成。
这种优化的根本出发点,是减小在数据页中的扫描量。覆盖索引,也是一种优化思路,出发点就是直接从二级索引中直接获取查询结果。
--------------------------------------------------------------------------------------------------------------------------------------------------------
Q3(cyb): SELECT DISTINCT
LoginId, SubId FROM T_Query WHERE 1 ORDER BY fenshu DESC LIMIT 61630, 10
fenshu 这一列不是主键,相似于京东商品、按评价数、按销量等不一样方式排序。
该表是基于其它表创建的查询表,总记录大约20万,这个是翻页查询SQL
A3(俞月):这条SQL从实现的功能而言,其实不必加ORDER BY fenshu DESC 的。
distinct只能返回它的目标字段,而没法返回其它字段。 因此在SELECT DISTINCT LoginId, SubId FROM T_Query中取出的是 LoginId,SubId不重复的行。也就是说,必须LoginId和SubId都相同才会被排除。
作个测试:
mysql> select * from tb;
+------+---------+-------+------+
| id | LoginId | SubId | vist |
+------+---------+-------+------+
| 1 | 123 | 21 | 78 |
| 2 | 43 | 71 | 78 |
| 3 | 43 | 21 | 78 |
| 2 | 43 | 71 | 78 |
| 3 | 43 | 21 | 78 |
| 2 | 43 | 71 | 78 |
| 5 | 73 | 21 | 78 |
| 2 | 55 | 67 | 78 |
| 1 | 98 | 21 | 78 |
+------+---------+-------+------+
9 rows in set (0.01 sec)
mysql> select distinct LoginId,SubId from tb ;
+---------+-------+
| LoginId | SubId |
+---------+-------+
| 43 | 21 |
| 43 | 71 |
| 55 | 67 |
| 73 | 21 |
| 98 | 21 |
| 123 | 21 |
+---------+-------+
6 rows in set (0.00 sec)
mysql> select distinct LoginId,SubId from tb where 1 order by vist;
+---------+-------+
| LoginId | SubId |
+---------+-------+
| 43 | 21 |
| 43 | 71 |
| 55 | 67 |
| 73 | 21 |
| 98 | 21 |
| 123 | 21 |
+---------+-------+
6 rows in set (0.00 sec)
这里若是建了(LoginId,SubId)便可避免distinct的建立临时表,避免排序。
mysql> explain select distinct LoginId,SubId from tb;
+----+-------------+-------+-------+------------------------------+-------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------------+-------------+---------+------+------+-------------+
| 1 | SIMPLE | tb | index | login_subId,login_subId_vist | login_subId | 10 | NULL | 9 | Using index |
+----+-------------+-------+-------+------------------------------+-------------+---------+------+------+-------------+
1 row in set (0.00 sec)
因此,对于您给出的SQL,个人建议是改写SQL为:
SELECT DISTINCT
LoginId, SubId
FROM
T_Query
LIMIT 61630, 10
并添加索引( LoginId, SubId),这里一方面能够利用到索引避免临时表、排序;另外一方面其实也是覆盖索引。
若是您发现去掉ORDER BY fenshu DESC 不符合您的业务需求,那么就须要考虑一下distinct的用法是否正确? select出来的结果集是不是您真实须要的。
另外须要提到一点:
您发给个人这张表,索引用法有点问题,建了不少没必要要的索引。
假如建了(A),(A,B),(A,B,C)三个索引,其实(A),(A,B)都是不须要的。
--------------------------------------------------------------------------------------------------------------------------------------------------------
Q4(华夏一剑): mysql如何实现like '%民生%' 这样的有效索引查询。
一、我建立一个表: CREATE TABLE tb_news
( id
bigint(20) NOT NULL auto_increment, title
varchar(100) default NULL, content
mediumtext, keywords
varchar(50) default NULL, PRIMARY KEY (id
) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312
二、同时建立索引: create index tb_news_title on tb_news(title);
三、有10万条数据,实现按标题title的模糊查询如: select id,title from news where title like '%民生%';
四、我知道: select id,title from news where title like '民生%'; 索引是有效的。但是select id,title from news where title like '%民生%'; 索引就无效了。
有什么好的办法解决标题title的模糊查询。
A4(俞月):在您给的例子中,select id, title from tb_news where title like '%mal%'; 是能够走上索引的,而且是覆盖索引。
innodb表的二级索引上存储了主键值,上面的SQL语句只须要查询id(主键字段)和title,因此扫描二级索引字段就能够获取到结果,不要再返回主键索引读取数据了。
mysql> explain select id, title from tb_news where title like '%mal%';
+----+-------------+---------+-------+---------------+---------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+---------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tb_news | index | NULL | tb_news_title | 203 | NULL | 5 | Using where; Using index |
+----+-------------+---------+-------+---------------+---------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
而相似这种,select * from tb_news where title like '%mal%';会走全表扫描。
mysql> explain select * from tb_news where title like '%mal%';
+----+-------------+---------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | tb_news | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
经过覆盖索引能够得到性能上的必定优化,可是在数据量特别大,请求频繁的业务场景下不要在数据库进行模糊查询;
非得使用数据库的话 ,建议不要在生产库进行查询,能够在只读节点进行查询,避免查询形成主业务数据库的资源消耗完,致使故障;
可使用MySQL自带的全文检索,或者一些开源的搜索引擎技术,好比sphinx.
Mysql是如何进行查询的,见下图:
致使一个查询可能跟咱们认为那样不同的主要缘由就是,mysql的查询优化器很复杂。Mysql服务器和客户端以前的通讯采用半双工的协议,意思是比如两我的传球,球在某个时间点,只可能被一我的控制。一端发消息后,另外一端彻底接受到消息才能响应。
查询缓存必需要要求查询和缓存中的查询每一个字节都同样,这跟hibernate二级缓存的sql缓存是同样的。
查询优化器都能作什么事情呢?
查询优化器是根据要执行成本,来选择更好的查询计划。可是其依靠的统计信息可能不全或者不许确,有可能获得不是最优的效果,触发函数,存储过程,就没法考虑。
优化器能够定义表关联的顺序,MYSQL查询使用循环嵌套的方式来执行查询,那么先执行大表的查找,再关联小表,循环的次数会更少。
能够将外join,转化为内部join.
优化count(),Min(),MAX();索引很能帮助来进行这类统计,好比寻找最小值,只须要找到最左端的值。特别重要的是,在没有where条件的状况下,count(“*”)直接从存储引擎获取,myISAM将其做为一个变量存起来。
对in列表优化,会先对in中的列表进行排序,而后使用二分法来验证是否知足某行是否知足该in条件。
存储引擎给查询执行引擎提供了基本的底层接口,存储引擎共有的特性则由服务器层实现,好比时间,日期,函数,视图,触发器等。
使用explaing工具很是重要,其结果中的type表示联结表的类型,全表扫描,索引扫描,范围扫描,惟一索引扫描,常数引用,查询的速度依次变的更快。
可是不少优化的策略并非绝对的,若是那个帖子上写的很绝对,那他通常都在瞎扯,我以为须要用explain进行分析。 一样将某个很复杂的查询进行分解,将会减小锁的竞争。让应用层作更多的事,或者更擅长的事,也是一个很好的策略。将某些逻辑处理交给应用层处理也颇有帮助。