当前位置: 移动技术网 > IT编程>数据库>Mysql > MySQL数据库表的优化与列类型选择、索引优化策略讲解

MySQL数据库表的优化与列类型选择、索引优化策略讲解

2018年01月02日  | 移动技术网IT编程  | 我要评论
MySQL优化 表的优化与列类型选择 一、表的优化 定长与变长分离 如id int, 占4个字节,char(4)占4个字符长度,即:每一个单元值占的字节是固定的。核心且常用

MySQL优化

表的优化与列类型选择

一、表的优化

定长与变长分离

如id int, 占4个字节,char(4)占4个字符长度,即:每一个单元值占的字节是固定的。核心且常用的字段,宜建成定长,放在一张表中。

而varchar、text、blob等。这种变长的字段,适合单放一张表中,用主键与核心表关联起来。 常用字段和不常用字段要分离。

需要结合网站具体业务来分析字段的查询场景,查询频度低的字段,单拆出来。 在1对多,需要关联统计的字段上,添加冗余字段。

二、列的选择

字段类型优先级。整型>date、time>enum,char>varchar>blob、text

列特点分析:

整型:定长,没有国家地区之分,没有字符集差异。比如:tinyint 1、2、3、4、5 <-char(1) a、b、c、d、e, 从空间上都是占一个字节,但是order by排序,前者更快。因为后者需要考虑字符集和校对集(就是排序规则)。 time:定长,运算快,节省空间。考考虑时区,写sql时不方便,where ‘2017-09-11’。 enum:能起约束值的目的,内部用整型来存储,但是与char联查时,内部要经历串与值的转化。 char:定长,考虑字符集和(排序)校对集。 varchar:不定长要考虑字符集的转换和排序时的校对集,速度慢。 text/Blob:无法使用内存临时表(排序等操作只能在磁盘一进行)。

如:性别,以utf8为例
1. char(1) 3个这字长字节。
2. enum(‘男’,’女’) 内部转成数字来存,多了一个转换过程。
3. tinyint() 定长一个字节 0、1、2。

够用就行,不要慷慨(如smallint, varchar(N))

大的字段浪费内存影响速度。

以年龄为例:tinyint unsigned not null,用存储到255岁足够用,用int会浪费三个字节。
varchar(10)和varchar(100)存储的内容相同,但在表联查时,后者会花更多内存。

尽量避免用NULL()

NULL不利于索引,要用特殊字节来标注,在磁盘上占据的空间也更大,mysql5.5已对NULL做改进,但查询仍然不便

例如:

可以建立两张字段相同的表,一个允许为NULL,一个不允许为NULL,各自加入一万条数据,查看索引文件的大小,可以发现为NULL的索引要大一些。 另外:NULL也不便于查询

where 列名 = null; where 列名 != null; 都查不到值 where 列名 is null 或 is not null 才能查到值

Enum列的查询

enum列在内部是用整型来储存的。 enum列与enum列相关联速度最快。 enum列比(var)char 的弱势:在碰到与char关联时,要转化,要花时间; 优势:当char非常长时,enum仍然是整型固定长度。当查询量越大时enum优势越明显。 enum与char/varchar 关联,因为要转化,速度要比enum -> enum、char -> char要慢,但有时也这样用:数据量特别大时,可以节省IO

索引优化策略

一、索引类型

B-tree 索引

名叫B-tree索引,大的方面看都用的是平衡树,但具体实现上,各引擎稍有不同,

比如:严格的说,NDB引擎使用的是T-tree,Myisam,InnoDB中默认的是B-tree索引,但抽象一下B-tree系统可理解为“排好序的快速查找结构”。

B-tree的常见误区

在where常用的列上都加上索引。例如:where id = 3 and price 100; 查询id为3价格大于100的

id和price上都加上索引 只能用上id和price索引,因为是独立的索引,同时只能使用上1个 在多列上建立索引后,查询哪个列,索引都将发挥作用

多列索引上,索引发挥作用,需要满足左前缀要求,以index(a,b,c)为例(注意只和顺序有关),见下表

SQL语句 索引能否发挥作用
where a = 3 a列有用
where a = 3 and b = 4 a、b列都有用
where a = 3 and b = 4 and c = 5 a、b、c 三列都有用
where b = 3 / where c = 4 都没用
where a = 3 and c = 5 a有用,c没用
where a = 3 and b > 4 and c = 5 a、b有用,c没用
同上:where a = 3 and b = like ‘xx%’ and c = 5 a、b有用,c没用

2. Hash 索引

在memory表中默认的是hash索引,hash的理论查询时间复杂度为O(1)。

hash 查询的缺点:

索引是hash函数计算后的随机结果,如果是在磁盘上放置数据,比如以id为例,那么随着id增长,id对应的行,在磁盘上随机放置。 无法对范围查询进行优化 无法利于前缀索引。比如,在B-tree中field列的值“helloworld”,并加索引查询 xx = helloworld,自然可以利用索引 xx = hello 也可以利用索引。因为hash(“helloworld”)和hash(“hello”),两者的关系为伪随机。 排序也无法优化 必须回行,就是说通过索引只能拿到数据位置,必须要再通过这个地址再回到表中拿数据。

面试题

商品表,有主键good_id,栏目 cat_id,价格price; 在价格上加了牵引,按价格查询时还是很慢,什么原因,怎么解决?

答:实际场景中一个电商网站商品分类很多,直接在所有商品中,按价格查询商品是极少的,一般客户都是来到分类下再查询

改正:去掉单独的price列的索引,加(cat_id, price)复合索引,再查询; 如果根据日志统计,发现好多人这样查:

电脑 –> 品牌 –> 价格 index(cat_id, brand_id, price)

二、非聚簇索引和聚簇索引

非聚簇索引

Myisam引擎是使用非聚簇索引 聚簇索引

innodb 的主索引文件上,直接存放该行数据,称为聚簇索引,次索引指向对主键的引用

myisam 中,主索引和次索引,都指向物理行(磁盘位置)

注意:

主键索引,即存储索引值,又在叶子中存储行的数据。 如果没有主键(primary key),则会 Unique key 做主键。 如果没有unique key,则系统生成一个内部的rowid 做主键。 像innodb中,主键的索引结构中,即存储了主键值,又存储了行数据,这种结构称为“聚簇索引”。

聚簇索引:

优势:根据主键查询条目较少时,不用回行(数据就在主键节点下面) 劣势:如果碰到不规则数据插入时,造成频繁的页分裂

索引覆盖

索引覆盖是指:如果查询的列恰好是索引的一部份,那么查询只需要在索引文件上进行,不需要再到磁盘上找数据。这种查询速度非常快,称为“索引覆盖”。

理想索引

查询频繁 区分度高:100万用户性别上基本男女各50万,区分度低 长度小:牵引长度直接影响索引文件大小,影响增删改的速度,并间接影响查询速度(占用内存多) 尽量能覆盖常用字段

建索引方法:针对列中的值,从左到右截取部分,来建索引

截的越短,重复度越高,区分度越低,索引效果越不好 截的越长,重复度越低,区分度越高,索引效果越好,带来影响也越大,增删改变慢,并间接影响查询速度。

所以:我们要在 区分度 + 长度 两者取得平衡

惯用手法:截取不同长度测试其区分度;

例如:
mysql> select count(distinct left(coulm, 6)) / count(*) from table;

索引和排序

排序可能发生的2种情况:

对于覆盖索引,直接在索引上查询时,就是有顺序的,using index,在 innodb引擎中,沿着索引字段排序,也有自然排序的,对于myisam引擎,如果按某索引字段排序。

如id,但取出的字段中,有未索引字段,如goods_name,myisam的做法,不是 索引 -> 回行…
而是先取出所有行,再进行排序。

先取出数据,形成临时表做filesort(文件排序,但文件可能在磁盘上,也可能在内存中)

using where:按照字段索引取出的结果,本身就是有序的 using filesort:用到文件排序,即取出的结果两次排序

争取目标: 取出来的数据本身就是有序的,利用索引来排序。

重复索引和冗余索引

重复索引:是指在同一个列,或者顺序相同的,几个列,建立多个索引,称为重复索引。其没任何帮助,只会增大索引文件,拖慢更新速度,去掉。 冗余索引:冗余索引是指2个索引覆盖的列有重叠。比较常见。

索引碎片和维护

在长期的数据更改过程中,索引文件和数据文件,都将产生空洞,形成碎片。我们可以通过一个nop操作(不产生对数据影响的实质操作)来修改表。

比如:表的引擎为innodb,可以 alter table xxx engine innodb
optimize table 表名,也可以修复

注意: 修复表的数据及索引碎片,就会把所有的数据文件重新整理一遍,使之对齐,这个过程,如果表的行数比较大,也是非常耗费资源的操作。所以,不能频繁修复。如果表的update操作很频繁,可以按周、月来修复。如果不频繁,可以更长的周期来修复。

explain 列分析

id select_type table type possible_keys key key_len ref rows Extra
查询编号 simple:不含子查询 查询针对的表 all:从表的第一行,逐行扫描,运气还好可能扫描到最后一行 可能用到的索引 最终使用的索引 使用的索引的最大长度 当连接查询时,表字段之间的引用关系 估计要扫描多少行 index:用到了索引覆盖,效率很高
  primary:含子查询或派生查询 实际表名 index:性能比all稍好,通俗的说:all是扫描所有的数据行,相当于data_all; index是扫描所有的索引节点。相当于 index_all 注意:系统估计可能用到的几个索引,但最终只能用一个         using where:只靠索引定位不了,不要where判断一下
  subquery:非from子查询 表的别名 索引覆盖的情况下能利用上索引数据,但利用不上索引查找,必须全索引扫描。 mysql> select goods_id from goods where goods_id + 1 > 30           using temporary:指用上了临时表,group by 与 order by不同列时,或group by,order by另的表的列。
  derived:from型子查询 derived:派生表 2.是利用索引进行排序,但是取出所有节点。mysql> select goods_id from goods order by goods_id desc; 分析:没有加where条件,就得取所有索引节点。同时,又没有回行,只取索引节点,再排序,经过所有节点           using filesort:文件排序(文件可能在磁盘,也可能在内存)
  union null:直接计算,不走表 rang:查询时能根据索引做范围扫描           注:如果取出的列包含text,或者更大的如mediumtext等,filesort会发生在磁盘上
  union result   ref:通过索引列可以直接引用某些数据行           show status like ‘%_table%’ 查询是否发生在磁盘上
      eq ref:通过索引列可以直接引用某一数据行            
      const、system、null:查询优化到常量级别,甚至不需要查找时间            

in型子查询引出的陷阱

mysql的查询优化器,针对in做了优化,被改成了exists子查询的执行效果,当表越大,查询越慢。exists子查询和in子查询在mysql底层相互转换。

改进:用连接查询来代替子查询。

from子查询

注意:内层from语句查到的临时表,是没有索引的。所以,from的返回内容要尽量小,需要排序,在内层先排好序。

count优化

误区:myisam 的count()非常快!
答:是比较快,但仅限于查询表的“所有行”比较快,因为myisam对行数进行了存储。一旦有条件的查询,速度就不再快了,尤其是where条件的列上没有索引。 假如,id<100的商家都是我们内部测试的,我们想查查真实的商家有多少?

小技巧:

select count(id) from table; 快 select count(id) from table where id < 100; 快 select( (select count(id)from table) - (select count(id) from table where id < 100) ); 快

group by

注意:

分组用于统计,而更是用于去重的
不重复的行,分组统计数据,而不要让查询产生N多重复数据。group by的列要有索引,可以防止临时表和文件排序。
order by 的列要和group by的列一致,否则也会引起临时表。

以A、B表连接为例,主要查询A表的列
那么group by, order by的列尽量相同,而且列应该显示声明为A的列

例:mysql> select A.id, A.cat_id from inner join B group by A.cat_id order by A.cat_id;

union 优化

union总是要产生临时表

注意:

union的子句条件要尽量具体,即要查询更少的行 子句的结果在内存时并成结果集,需要去重,去重就要先排序,而加了all之后,不需要去重,union尽量加all

limit 及翻页优化

limit offset,N 当offset非常大时,效率及低。原因是因为mysql并不是跳过offset行,然后单取N行,而是取offset+N行,放弃前offset行,返回N行。效率及低,当offset越大,效率越低。

优化办法:

从业务上解决:不允许翻过100页,以百度为例,一般翻到70页左右。

不用offset,用条件查询

比如: mysql> select id, name from table limit 10000000, 10; 可以这样
mysql> select id, name from table where id > 10000000 limit 10;

如果数据要作物理删除,无法用id作为条件查询,不要offset精确查询,还不限制用户分页,可以这样:
分析:优化思路:不查,少查,查索引,少取列,我们现在必须要查,则只查索引,不查数据,得到id,再用尖支查具体条目,这种技巧就是:延迟关联

比如:mysql> select id,name from table inner join (select id from table limit 10000000, 10) as tmp on table.id = tmp.id;

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网