MySQL优化

1 设计优化

1.1 字段设计

1.尽量使用整型表示字符串。

2.decimal不会损失精度,存储空间会随数据的增大而增大。double占用固定空间,较大数的存储会损失精度。

3.尽可能选择小的数据类型和指定短的长度。

4.尽可能使用 not null。

5.单表字段不宜过多。

1.2 数据库设计三范式

  • 第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。

  • 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。

  • 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

1.3 存储引擎选择

1.3.1 存储差异

  MyISAM Innodb
文件格式 数据.MYD和索引.MYI分别存储 数据和索引集中存储.ibd
文件能否移动 能,一张表对应.frm、MYD、MYI文件 否,关联的还有data下文件
记录存储顺序 按记录插入顺序保存 按主键大小有序插入
空间碎片 产生 不产生
事务 不支持 支持
外健 不支持 支持
锁支持 表级锁定 行级锁定,表级锁定

1.3.2 选择依据

MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。

Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键保证数据完整性。比如OA自动化办公系统。

2 索引优化

2.1 概念

关键字与数据的映射关系称为索引(包含关键字和对应的记录在磁盘中的地址)。

2.2 索引类型

  • 普通索引:对关键字没有限制

  • 唯一索引:要求记录提供的关键字不能重复

  • 主键索引:要求关键字唯一且不为null

2.3 索引的存储结构

  • BTree

    多路查找平衡树,BTree的一个node可以存储多个关键字,node的大小取决于计算机的文件系统,因此我们可以通过减小索引字段的长度使结点存储更多的关键字。

  • B+Tree聚簇结构

    在MySQL中,仅仅只有Innodb的主键索引为聚簇结构,其它的索引包括Innodb的非主键索引都是典型的BTree结构。

  • 哈希索引

    在索引被载入内存时,使用哈希结构来存储。

2.4 创建索引

  • 创建表之后建立索引

    -- 更改表结构alter table user_index-- 创建一个first_name和last_name的复合索引,并命名为nameadd key name (first_name,last_name),-- 创建一个id_card的唯一索引,默认以字段名作为索引名add UNIQUE KEY (id_card),-- 鸡肋,全文索引不支持中文add FULLTEXT KEY (information);
  • 创建表时指定索引

    CREATE TABLE user_index2 (    id INT auto_increment PRIMARY KEY,    first_name VARCHAR (16),    last_name VARCHAR (16),    id_card VARCHAR (18),    information text,    KEY name (first_name, last_name),    FULLTEXT KEY (information),    UNIQUE KEY (id_card));

2.5 索引优化

可以通过explain selelct来分析SQL语句执行前的执行计划

  • 建立基础索引:在where、order by、join字段上建立索引。

  • 优化,组合索引:基于业务逻辑

  • 如果条件经常性出现在一起,那么可以考虑将多字段索引升级为复合索引

  • 如果通过增加个别字段的索引,就可以出现索引覆盖,那么可以考虑为该字段建立索引

  • 查询时,不常用到的索引,应该删除掉

简要解释下explain各个字段的含义

  • id : 表示SQL执行的顺序的标识,SQL从大到小的执行

  • select_type:表示查询中每个select子句的类型

  • table:显示这一行的数据是关于哪张表的,有时不是真实的表名字

  • type:表示MySQL在表中找到所需行的方式,又称“访问类型”。常用的类型有:ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)

  • possible_keys:指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用

  • Key:key列显示MySQL实际决定使用的键(索引),如果没有选择索引,键是NULL。

  • key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)

  • ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

  • rows:表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,理论上行数越少,查询性能越好

  • Extra:该列包含MySQL解决查询的详细信息

2.6 索引失效的场景

1.当语句中带有or的时候 即使有索引也会失效。

select * from USER where name='xzz' or age=16;

2.当语句索引 like %在前面的时候索引失效(注意:如果上句为 like ‘some%’此时索引是生效的)。

select *  from  USER where name like '%xzz' ;

3.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。

select * from USER where name=123;

4.mysql联合索引有最左原则,需要按照顺序。将name和age设置为联合索引。

select * from USER where age=11 and name='xzz';

5.在索引上进行计算操作。

select * from USER where age-1>11;

6.负向查询不能使用索引

select name from user where id not in (1,3,4);

3 SQL语句优化

3.1 sql优化

  • 数据库导入大量数据时,可以先禁用索引和约束alter table table-name disable keys,导入后开启alter table table-name enable keys

  • limit offset,rows 尽量保证不要出现大的offset,比如limit 10000,10相当于对已查询出来的行数弃掉前10000行后再取10行。

  • order by 不要用rand(),效率低。在应用程序中,将随机的主键生成好,去数据库中利用主键检索。

  • select() count()少用*,如果确定只查询一条数据,可以用limit 1。

3.2 慢查询日志

用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。

3.2.1 开启慢查询日志

配置项:slow_query_log

可以使用show variables like ‘slov_query_log’查看是否开启,如果状态值为OFF,可以使用set GLOBAL slow_query_log = on来开启,它会在datadir下产生一个xxx-slow.log的文件。

3.2.2 设置临界时间

配置项:long_query_time

查看:show VARIABLES like 'long_query_time',单位秒

设置:set long_query_time=0.5

实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉

3.2.3 查看日志

一旦SQL超过了我们设置的临界时间就会被记录到xxx-slow.log

3.3 profile信息

3.3.1 查看状态,并开启

show variables like 'profiling';--查看状态
set profiling=on;--开启
show profiles;--查看profile信息

3.3.2 通过ID查看某条sql所有详细步骤的时间

通过show profiles;查找到ID,使用show profile for query Query_ID查询经过了哪些步骤,各消耗了多场时间。

4 横向扩展(MySQL集群、负载均衡、读写分离)

4.1 数据库分区

当数据量较大时(一般千万条记录级别以上),MySQL的性能就会开始下降,这时我们就需要将数据分散到多组存储文件,保证其单个文件的执行效率。

4.1.1 分区管理语法

  • range/list

    alter table article_range add partition(
        partition p201811 values less than (1543593599) -- select UNIX_TIMESTAMP('2018-11-30 23:59:59')    
        -- more
    );
    alter table article_range drop PARTITION p201808;--删除分区后,分区中原有的数据也会随之删除!
  • key/hash

    alter table article_key add partition partitions 4;--新增
    alter table article_key coalesce partition 6;--删除不会删除数据

4.1.2 mysql分区算法

  • hash(field)

    相同的输入得到相同的输出。输出的结果跟输入是否具有规律无关。仅适用于整型字段。

    create table article(    
        id int auto_increment PRIMARY KEY,    
        title varchar(64),   
        content text
    )PARTITION by HASH(id) PARTITIONS 10;
  • key(field)

    hash(field)的性质一样,只不过key是处理字符串的,比hash()多了一步从字符串中计算出一个整型在做取模操作。

    create table article_key(
        id int auto_increment,
        title varchar(64),
        content text,
        PRIMARY KEY (id,title)  -- 要求分区依据字段必须是主键的一部分
        )PARTITION by KEY(title) PARTITIONS 10;
  • range算法

    是一种条件分区算法,按照数据大小范围分区(将数据使用某种条件,分散到不同的分区中)。

      create table article_range(
          id int auto_increment,
          title varchar(64),
          content text,
          created_time int,   -- 发布时间到1970-1-1的毫秒数
          PRIMARY KEY (id,created_time)   -- 要求分区依据字段必须是主键的一部分
      )charset=utf8PARTITION BY RANGE(created_time)(
          PARTITION p201808 VALUES less than (1535731199),    -- select UNIX_TIMESTAMP('2018-8-31 23:59:59')    
          PARTITION p201809 VALUES less than (1538323199),    -- 2018-9-30 23:59:59
          PARTITION p201810 VALUES less than (1541001599) -- 2018-10-31 23:59:59
      );
  • list算法

    也是一种条件分区,按照列表值分区(in (值列表))。

      create table article_list(
          id int auto_increment,
          title varchar(64),
          content text,
          status TINYINT(1),-- 文章状态:0-草稿,1-完成但未发布,2-已发布
          PRIMARY KEY (id,status) -- 要求分区依据字段必须是主键的一部分
          )charset=utf8PARTITION BY list(status)(
          PARTITION writing values in(0,1),   -- 未发布的放在一个分区
          PARTITION published values in (2)   -- 已发布的放在一个分区
      );
      insert into article_list values(null,'mysql优化','内容示例',0);
      flush tables;

4.2 大表分表

  • 垂直分表: 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。

    • 垂直拆分的优点: 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。

    • 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;

  • 水平分表: 保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。 数据库分片的两种常见方案:

    • 客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。

    • 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。

4.3 集群

单中心双机的常见方案有以下这些:

  • 一主一备的架构(主备式)

    将数据库部署到两台机器,其中一台机器(代号A)作为日常提供数据读写服务的机器,称为「主机」。另外一台机器(代号B)并不提供线上服务,但会实时的将「主机」的数据同步过来,称为「备机」。一旦「主机」出了故障,通过人工的方式,手动的将「主机」踢下线,将「备机」改为「主机」来继续提供服务。

    缺点:人工干预。

  • 一主一从的架构(主从式)

    主从式架构中的「从机」虽然也在随时随刻提供服务,但是它只提供「读」服务,并不提供「写」服务。「主机」会实时的将线上数据同步到「从机」,以保证「从机」能够正常的提供读操作。

    主从双机自动切换: 当主机出现故障后,从机能够自动检测发现。同时从机将自己迅速切换为主机,将原来的主机立即下线服务,或转换为从机状态。

    缺点:数据同步延迟。

  • 互为主从的架构(主主式)

    互为主从的架构是指两台机器自己都是主机,并且也都是作为对方的从机。两台机器都提供完整的读写服务,因此无需切换,客户机在调用的时候随机挑选一台即可,当其中一台宕机了,另外一台还可以继续服务。

    缺点:双向复制,数据延迟或丢失。

4.4 读写分离

读写分离是依赖于主从复制,而主从复制又是为读写分离服务的。因为主从复制要求slave不能写只能读(如果对slave执行写操作,那么show slave status将会呈现Slave_SQL_Running=NO,此时你需要按照前面提到的手动同步一下slave)。

4.4.1 定义两种连接

就像我们在学JDBC时定义的DataBase一样,我们可以抽取出ReadDataBase,WriteDataBase implements DataBase,但是这种方式无法利用优秀的线程池技术如DruidDataSource帮我们管理连接,也无法利用Spring AOP让连接对DAO层透明。

4.4.2 使用Spring AOP

在调用DAO接口时根据接口方法命名规范(增addXXX/createXXX、删deleteXX/removeXXX、改updateXXXX、查selectXX/findXXX/getXX/queryXXX)动态地选择数据源(读数据源对应连接master而写数据源对应连接slave),那么就可以做到读写分离了。(可以参考:Spring 动态切换、添加数据源实现以及源码浅析

4.5 负载均衡

4.5.1 负载均衡算法

  • 轮询

  • 加权轮询:按照处理能力来加权

  • 负载分配:依据当前的空闲状态(但是测试每个节点的内存使用率、CPU利用率等,再做比较选出最闲的那个,效率太低)

4.5.2 高可用

在服务器架构时,为了保证服务器7x24不宕机在线状态,需要为每台单点服务器(由一台服务器提供服务的服务器,如写服务器、数据库中间件)提供冗余机。(推荐:mysql+mycat搭建稳定高可用集群

对于写服务器来说,需要提供一台同样的写-冗余服务器,当写服务器健康时(写-冗余通过心跳检测),写-冗余作为一个从机的角色复制写服务器的内容与其做一个同步;当写服务器宕机时,写-冗余服务器便顶上来作为写服务器继续提供服务。对外界来说这个处理过程是透明的,即外界仅通过一个IP访问服务。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章