mysql优化笔记 ——表的设计及索引优化

概览

  • 表的设计合理化(符合3NF,有时需要反3NF)
  • 添加合理且适当的索引
  • 利用分表技术(水平分割、垂直分割)
  • 读写分离
  • 存储过程(模块化编程,可以提高速度)
  • 对mysql服务器硬件升级
  • 定时的去清理不需要的数据,定时进行碎片整理(其中若引擎为myISAM必须清理)

表的设计合理化

  • 3NF(3范式) – 一步步满足
    1NF:表的列具有原子性.不可再分解(即列的信息不能分解)只要数据库是关系型数据库,就自动的满足1NF数据库分类:关系型数据库,非关系型数据库(特点:面向对象或者集合),NoSql数据库。
    2NF:表中的记录是唯一的(通常我们设计一个主键来实现)
    3NF:表中不要有冗余数据,也就是说如果表的信息能够单独推导出来就不应该单独的设计一个字段来存放(反3NF:在表的1对N的情况下,为了提高速率,可能会在1这个表中增加某些字段)
  • 数据类型优化
    a. 更小的通常更好:尽量使用可以正确存储数据的最小数据类型。
    b. 尽量选取简单数据类型:如整型比字符串操作代价低。
    c. 尽量避免NULL(对于InnoDB例外,因为其使用单独的位(bit)存储NULL值,所以对于稀疏数据有很好的空间使用率):通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。因为如果在包含NULL的列上建立索引、索引统计和值比较都比较复杂。当可为NULL的列被索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引(如只有一个整数列的索引)变成可变大小的索引。

    数据类型选择

    整数类型

           TINYINT(8), SMALLINT(16), MEDIUMINT(24), INT(32), BIGINT(64),括号里为其类型对应的存储空间的位数。其存储范围为-2(N-1)到2(N-1)-1。如果将其类型设置为UNSIGNED属性,表示不允许负值,这大致可以使正数的上限提高一倍。
           整数计算一般使用64位的BIGINT整数,一些聚合函数除外,其使用DECIMAL或DOUBLE进行计算。
           mysql可以为整数类型指定宽度,如INT(11),但其对大多数应用没有意义:它不会限制值的合法范围,只是规定了mysql的一些交互工具(如mysql命令行客户端)用来显示字符的个数。对于存储和计算来说,INT(1)和INT(20)是相同的。

    实数类型

           FLOAT(4字节)和DOUBLE(8字节)类型支持使用标准的浮点运算进行近似计算。DECIMAL类型用于存储精确的小数,也可以存储比BIGINT更大的整数。

    VARCHAR和CHAR类型

           这两种是最主要的字符串类型。请注意:存储引擎存储CHAR或者VARCHAR值的方式在内存中和在磁盘上可能不一样,所以mysql服务器从存储引擎读出的值可能需要转换为另一种存储格式。
           VARCHAR类型用于存储可变长字符串,同时需要1或2个额外字节记录字符串的长度。由于其变长,所以可能会很容易产生内存碎片。
           CHAR类型是定长的。注意当字符串长度小于定长时,会用空格填充。因此需要注意使用concat等拼接字符串时,可能会截断已存储的字符串中尾部含有的空格。

    BLOB和TXET类型

           BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。
           字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。BLOB是SMALLBLOB的同义词,TEXT是SMALLTEXT的同义词。
           同其他类型不同,mysql把每个BLOB和TEXT值当做一个独立的对象处理。存储引擎在存储时通常会做特殊处理。当BLOB和TEXT值太大时,InnoDB会使用专门的‘外部’存储区域来进行存储,此时每个值在行内需要1 ~ 4个字节存储一个指针,然后在外部存储区域存储实际的值。

    ENUM —— 使用枚举代替字符串类型

    DATETIME和TIMESTAMP

           DATETIME保存大范围的值,从1001年到9999年,精度为秒。它把日期和时间封装到格式为YYYYMMDDHHMMSS的整数中,与时区无关。使用8字节存储空间。
           TIMESTAMP保存了从1970年1月1日午夜(格林尼治标准时间)以来的秒数,只能表示1970年到2038年。它和UNIX时间戳相同,其显示的值与时区有关。只使用4个字节的存储空间。
           可以通过FROM_UNIXTIME()函数把Unix时间戳转为日期,也可以通过UNIX_TIMESTAMP()函数将日期转为Unix时间戳。

    schema设计中的陷阱

    • 列太多
             mysql存储引擎API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列。从行缓冲将解码过的行转换成行数据结构的操作代价是非常高的。MyISAM的定长行结构实际上与服务器层的行结构正好匹配,所以不需要转换。然而,myISAM的变长行结构和InnoDB的行结构总是需要转换。转换的代价依赖于列的数量。
    • 太多的关联
    • 全能的枚举

添加合理且适当的索引

       在mysql中,索引是在存储引擎层而不是在服务器层实现的。

独立的列

       意思是索引列不能是表达式的一部分,也不能是函数的参数。

select actor_id from actor where actor_id + 1 = 5;
select ... where to_days(current_date) - to_days(date_clo) <= 10;

前缀索引和索引的选择性

       问题:如果索引很长的字符列,会死索引变得大且慢。
       解决:
       1、使用哈希索引
              哈希索引基于实现。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。
              在mysql中,只有memory引擎是显示支持哈希索引。
              InnoDB引擎有一个特殊的功能叫做“自适应哈希索引”。当InnoDB注意到某些索引值被使用的非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引。这是一个完全自动的、内部的行为,用户无法控制或者配置。
              创建自定义哈希索引:

// 使用CRC32()建立hash索引
select id from url where url = "https://www.baidu.com" and url_crc = CRC32("https://www.baidu.com");

create table pseudohash (
	id int unsigned not null auto_increment,
	url varchar(255) not null,
	url_crc int unsigned not null default 0,
	primary key(id)
);

delimiter $
create trigger pseudohash_crc_ins before insert on pseudohash for each row begin set new.url_crc= crc32(new.url);
end;
$
create trigger pseudohash_crc_upd before update on pseudohash for each row begin set new.url_crc= crc32(new.url);
end;
$
delimiter ;

// 使用MD5()函数返回值的一部分作为自定义hash函数 —— 因为SHA1()和MD5()计算出来的哈希值是非常长的字符串,会浪费大量空间,比较时也比较慢
select conv(right(MD5('https://www.baidu.com/'), 16), 16, 10) as HASH64;

       2、索引开始的部分字符
       优点:
              索引开始的部分字符可以大大节约索引空间,从而提高索引效率,但是会降低索引的选择性(索引选择性是指不重复的索引值和数据表记录总数的比值,唯一索引的选择性是1)。
              选取前缀长度时要注意其选择性要接近完整列的选择性。其中一种方式是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。

// 计算完整列的选择性
select count(distinct city)/count(*) from city_demo;
// 计算各种前缀选择性
select count(distinct left(city, 3))/count(*) as sel3,
       count(distinct left(city, 4))/count(*) as sel4,
       count(distinct left(city, 5))/count(*) as sel5,
       count(distinct left(city, 6))/count(*) as sel6,
       count(distinct left(city, 7))/count(*) as sel7
       from city_demo;
// 创建前缀索引
alter table city_demo add key(city(7));

              有时候只看这种平均选择性不一定够,还需要看一下最坏情况下的选择性(因为数据可能分部不均匀)。
       缺点:
              mysql无法使用前缀索引做order by和group by,也无法使用前缀索引做覆盖扫描(如果where条件的列和返回的数据在一个索引中,那么不需要回查表,那么就叫覆盖索引)。

多列索引 和 列顺序

       常见错误:为每个列创建独立的索引,或者按照错误的顺序创建多列索引。
       要注意使用explain查看一下,哪个索引起作用。
       在一个多列B-Tree的索引中,索引列的顺序意味着索引首先按照最左侧列进行排序,其次是第二列,等等。所以,索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的order by、group by和distinct等子句的查询需求。
对于优化where查询可以将选择性最高的列放在前面。

聚簇索引

       聚簇索引不是单独的索引类型,而是一种数据存储方式。InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree
索引和数据行。当表有聚簇索引时,他的数据行实际上放在索引的叶子页中。

索引操作

添加

  • 在哪些列上适合添加索引
    较为频繁的作为查询条件字段应该创建索引
    唯一性太差的字段不适合单独创建索引(如性别)
    更新非常频繁的字段不适合创建索引
    不会出现在where子句中的字段不应该创建索引
  • 使用索引的注意事项:
    把dept表中,我增加几个部门:
    alter table dept add index my_ind (dname,loc); // dname 左边的列,loc就是右边的列
    说明,如果我们的表中有复合索引(索引作用在多列上), 此时我们注意:
           1,对于创建的多列索引,只要查询条件使用了最左边的列,索引一般就会被使用。
    explain select * from dept where loc=‘aaa’\G 不会使用到索引
           2,对于使用like的查询,查询如果是 ‘%aaa’ 不会使用到索引,‘aaa%’ 会使用到索引。
           比如: explain select * from dept where dname like ‘%aaa’\G
           不能使用索引,即,在like查询时,关键的 ‘关键字’ , 最前面,不能使用 % 或者 _这样的字符., 如果一定要前面有变化的值,则考虑使用 全文索引->sphinx.
           3.如果条件中有or,即使其中有条件带索引也不会使用。换言之,就是要求使用的所有字段,都必须建立索引, 我们建议大家尽量避免使用or 关键字
    select * from dept where dname=’xxx’ or loc=’xx’ or deptno=45
           4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来。否则不使用索引。(添加时,字符串必须’’), 也就是,如果列是字符串类型,就一定要用 ‘’ 把他包括起来.
           5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引。
  1. 主键索引添加
    当一个表把某个列设为主键时,则该列为主键索引
    create table aaa(
    id int unsigned primary key auto_increment,
    name varchar(32) not null default’’);
    给已有的表创建主键
    alter table 表明 add primary key(列名);
    数据结构 .frm/数据 .MYD/索引 .MYI

  2. 普通索引
    一般来说,先创建表然后再常见普通索引
    create index 索引名 表(列1,列2);

    扫描二维码关注公众号,回复: 6111159 查看本文章
create table ccc(
id int unsigned,
name varchar(32) );
  1. 全文索引----FULLTEXT

全文索引主要是针对文字,文本的检索,比如文章,全文索引针对MYISAM有用

CREATE TABLE articles (
       id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
       title VARCHAR(200),
       body TEXT,
       FULLTEXT (title,body)
     )engine=myisam charset utf8;
INSERT INTO articles (title,body) VALUES
     ('MySQL Tutorial','DBMS stands for DataBase ...'),
     ('How To Use MySQL Well','After you went through a ...'),
     ('Optimizing MySQL','In this tutorial we will show ...'),
     ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
     ('MySQL vs. YourSQL','In the following database comparison ...'),
     ('MySQL Security','When configured properly, MySQL ...');

如何使用全文索引:

错误用法:
select * from articles where body like '%mysql%'; 【不会使用到全文索引】
证明:
explain  select * from articles where body like ‘%mysql%’

正确的用法是:
select * from articles where match(title,body) against('database'); 

说明
1 在mysql中,fulltext索引只针对myisam生效
2 针对英文生效2.->sphinx (coreseek) 技术处理中文
3 使用方法是 match(字段名) against (‘关键字’)
4.全文索引一个 叫 停止词, 因为在一个文本中,创建索引是一个无穷大的数,因此,对一些常用词和字符,就不会创建,这些词,称为停止词.
示例

  1. 唯一索引
    当表的某列被指定为unique时,该列就是一个唯一索引
create table ddd(id int primary key auto_increment , name varchar(32) unique);

这时, name 列就是一个唯一索引.
unique字段可以为NULL,并可以有多NULL, 但是如果是具体内容,则不能重复.
主键字段,不能为NULL,也不能重复.

在创建表后,再去创建唯一索引

create table eee(id int primary key auto_increment, name varchar(32));

create unique index 索引名 on 表名 (列表…);

查询索引

des 表明[该方法缺点是不能够显示索引名]
show index(es) from 表名
show keys from 表名

如何查看索引使用的情况:
show status like ‘Handler_read%’;
大家可以注意:
handler_read_key:这个值越高越好,越高表示使用索引查询到的次数。
handler_read_rnd_next:这个值越高,说明查询低效。
explain详解
explain 可以帮助我们在不真正执行某个sql语句时,就知道mysql怎样执行,通过这样使用,我们去分析sql指令.
explain详解

删除

alter table 表名 drop index 索引名
如果删除主键索引 alter table 表名 drop primary key

索引的实现原理及优缺点

原理示意图
原理示意图

索引使用注意事项
索引的代价:
磁盘占用
对dmi(update delete insert )语句的效率影响
引擎及索引

sql语句小技巧

  1. 在使用group by 分组查询是,默认分组后,还会排序,可能会降低速度.
  2. 有些情况下,可以使用连接来替代子查询。因为使用join,MySQL不需要在内存中创建临时表。
    select * from dept, emp where dept.deptno=emp.deptno; [简单处理方式]
    select * from dept left join emp on dept.deptno=emp.deptno; [左外连接,更ok!]

猜你喜欢

转载自blog.csdn.net/qq_28376741/article/details/86714498