• MySQL优化概述


    一. MySQL优化要点

      MySQL优化是一门复杂的综合性技术,主要包括:

      1 表的设计合理化(符合 3NF,必要时允许数据冗余) 

      2.1 SQL语句优化(以查询为主)

      2.2 适当添加索引(主键索引,唯一索引,普通索引(包括联合索引),全文索引)

      3 分表技术(水平分割,垂直分割)

      4 读写分离(写包括update/delete/insert)

      5 存储过程(模块化编程,提高执行速度)

      6 MySQL配置优化

      7 数据库服务器硬件升级

      8 定时数据清理,碎片整理(MyISAM)

    二. 3NF是什么

      1. 第一范式

      第一范式是最基本的范式。要求数据库表中的所有字段值都是不可分解的原子值,即要求列的原子性。

      2. 第二范式

      第二范式是建立在第一范式的基础之上的,要求数据库表中的记录(行)必须是唯一的,即要求行的唯一性。

      通常通过设计一个主键来实现(建议主键不要有具体的业务含义)。

      3. 第三范式

      满足第三范式必须要满足第二范式。要求非主键列必须直接依赖于主键,不能存在传递依赖,及表中不能有冗余数据。

      表中某字段的信息可以通过其他列推导出来,就不应该设计此列。

      反3NF:没有冗余的数据库表设计未必是最优设计,有时为了提高效率,需要降低范式标准,适当增加冗余字段。

    三. SQL语句优化

      1.定位慢查询(查找执行速度慢的SQL语句)

      ① 了解MySQL数据库运行状态

      > show status like 'uptime'  #查询数据库运行时间(单位:s)

      > show [session | global] status like '[com_select | com_insert | com_update | com_delete]' #查询SQL语句执行次数

        session | global :默认为 session ,表示当前 session 的语句执行次数;global 表示数据库运行以来所有次数。

      > show status like 'connections'  #查询当前

      > show status like 'slow_queries'  #显示慢查询次数

      ② 如何定位慢查询

      a.首先构造一张大表的数据,SQL语句如下:

    CREATE DATABASE temp0919;
    USE temp0919;
    
    #创建表DEPT
    CREATE TABLE dept( /*部门表*/
        deptno MEDIUMINT   UNSIGNED  NOT NULL  DEFAULT 0, 
        dname VARCHAR(20)  NOT NULL  DEFAULT "",
        loc VARCHAR(13) NOT NULL DEFAULT ""
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
    
    #创建表EMP雇员
    CREATE TABLE emp(
        empno  MEDIUMINT UNSIGNED  NOT NULL  DEFAULT 0, /*编号*/
        ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/
        job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/
        mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/
        hiredate DATE NOT NULL,/*入职时间*/
        sal DECIMAL(7,2)  NOT NULL,/*薪水*/
        comm DECIMAL(7,2) NOT NULL,/*红利*/
        deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/
    )ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
    
    #工资级别表
    CREATE TABLE salgrade
    (
        grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
        losal DECIMAL(17,2)  NOT NULL,
        hisal DECIMAL(17,2)  NOT NULL
    )ENGINE=MyISAM DEFAULT CHARSET=utf8;
    
    INSERT INTO salgrade VALUES (1,700,1200);
    INSERT INTO salgrade VALUES (2,1201,1400);
    INSERT INTO salgrade VALUES (3,1401,2000);
    INSERT INTO salgrade VALUES (4,2001,3000);
    INSERT INTO salgrade VALUES (5,3001,9999);
    
    
    #定义一个新的命令结束符合
    delimiter $$
    #删除自定的函数
    drop function rand_string $$
    
    #创建一个函数(随机产生字符串) 
    #rand_string(n INT) rand_string 是函数名 (n INT) //该函数接收一个整数
    create function rand_string(n INT) 
    returns varchar(255) #该函数会返回一个字符串
    begin 
        #chars_str定义一个变量 chars_str,类型是 varchar(100),默认值'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
        declare chars_str varchar(100) default
            'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
        declare return_str varchar(255) default '';
        declare i int default 0;
        while i < n do 
            set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
            set i = i + 1;
        end while;
        return return_str;
    end $$
    
    
    #函数: 随机产生部门编号
    drop function rand_num $$
    
    create function rand_num( )
    returns int(5)
    begin 
        declare i int default 0;
        set i = floor(10+rand()*500);
        return i;
    end $$
    
    
    #******************************************
    #向emp表中插入记录(海量的数据)
    drop procedure insert_emp $$
    
    #随即添加雇员[光标]  400w
    create procedure insert_emp(in start int(10),in max_num int(10))
    begin
        declare i int default 0; 
        #把autocommit设置成0,不自动提交
        set autocommit = 0;  
        repeat
            set i = i + 1;
            insert into emp values ((start+i) ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
        until i = max_num
        end repeat;
        commit;
    end $$
    
    
    delimiter ;
    #调用刚刚写好的函数, 4000000条记录,从100001号开始
    call insert_emp(100001,4000000);
    
    
    #**************************************************************
    #  向dept表中插入记录
    
    delimiter $$
    drop procedure insert_dept $$
    
    create procedure insert_dept(in start int(10),in max_num int(10))
    begin
        declare i int default 0; 
        set autocommit = 0;  
        repeat
            set i = i + 1;
            insert into dept values ((start+i) ,rand_string(10),rand_string(8));
            until i = max_num
        end repeat;
        commit;
    end $$
    
    delimiter ;
    call insert_dept(100,10);
    View Code  

      b. MySQL默认执行时间大于10s的语句为慢查询,此处修改为1s

      > show variables like 'long_query_time'  #显示慢查询时间标准

      > set long_query_time = 1  #修改慢查询时间,当前session有效

      c. 记录慢查询到日志文件中

      默认情况下MySQL不会记录慢查询日志,需要在启动MySQL服务时,指定相应的参数,才可以记录慢查询日志。

      两种启动方式如下:

      (1) 修改配置后启动(linux : my.conf,  windows : my.ini) 

    [mysqld]
    #慢查询日志文件位置, 此目录文件一定要有写权限
    log-slow-queries="/usr/local/mysql5.6/data/black-slow.log"
    #慢查询时间标准
    long_query_time = 2
    #没有使用到索引的查询也将被记录在日志中
    log-queries-not-using-indexes

        修改配置文件后重新启动MySQL服务:

        $ sudo $MYSQL_HOME/support-files/mysql.server start  # Linux/OSX

        > mysqld.exe   # Windows

      (2) MySQL客户端修改参数

        使用MySQL客户端登陆MySQL数据库

       > show variables like '%slow_query%'   #查看慢查询相关参数

       > show variables like 'long_query_time'    #查询慢查询时间标准

       > set global slow_query_log = ON;  #开启慢查询日志

       > set global long_query_time = 1;  #调整慢查询时间标准

       此时不需要重启MySQL服务,即可开启慢查询日志。

       

      2. 索引的维护

        索引维护相关讨论见下方第四节。

      3. 索引的使用

        ① 对于创建了联合索引的情况,只有当查询条件中使用了联合索引中最左边的列,索引才会被使用。  

    ALTER TABLE dept ADD INDEX union_idx (dname, loc);
    
    #如下则不会使用到此索引
    SELECT * FROM dept WHERE loc='Beijing';

        ② 对于使用 like 的查询,左模糊查询不会使用到相应列上的索引,及 like 查询条件的左侧不能为 '%aa' 或 '_aa' 的形式。

          如果一定要针对左模糊查询条件使用索引,可以考虑使用全文索引。

        ③ 如果查询条件中带有 or ,则要求 or 中涉及到所有列都有索引,否则不会使用索引。

          建议:尽量避免使用 or 关键字。

        ④ 如果创建索引的列的类型是字符串,则查询条件中必须使用字符串,才会使用索引。    

    ALTER TABLE dept ADD INDEX idx_name (dname);
    # 可以使用索引
    SELECT * FROM dept WHERE dname = 'Tom';
    # 不会使用索引
    SELECT * FROM dept WHERE dname = 235;

        ⑤ 如果MySQL判断使用全表扫描比使用索引快,则不会使用索引。(出现的情况少)

      4. explain指令的用法

       explain 指令可以查看SQL语句的执行计划,效果如下:

      

      说明:

        id :查询序列号

        select_type : 查询类型

        table:查询的表名

        type:扫描方式,all 代表全表扫描

        possible_keys:可能使用到得索引

        key:实际使用的索引

        rows:SQL语句扫描的行数

        Extra:额外信息,如排序方式等

      5 查看索引使用情况

      > show status like 'Handler_read%';

      

      handler_read_key:越高越好,表示使用索引查询的次数

      handler_read_rnd_next:越低越好,此值高说明查询低效

      6 SQL 优化小技巧

      ① 在查询语句中使用 group by 分组时,MySQL会默认对分组结果进行排序,可能会降低速度。使用 order by null 可以关闭排序。

      

      ② 有些情况下,使用连接代替子查询。使用 join 不需要在内存中创建临时表。

    四. 索引

      对于SQL优化来说,最重要的方式之一是添加索引。索引分为四种:主键索引,唯一索引,普通索引,全文索引。

      1. 创建索引

      ① 主键索引:当创建一张表时,指定某列位主键,即在该列上创建了一个主键索引。   

    CREATE TABLE aaa(
          id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
          name VARCHAR(50) NOT NULL DEFAULT ''   
    );
    
    CREATE TABLE bbb(
          id INT UNSIGNED,
          name VARCHAR(50) NOT NULL DEFAULT ''   
    );
    ALTER TABLE bob ADD PRIMARY KEY(id);

      ② 普通索引:先创建表,然后再创建普通索引,普通索引可以单独一列或多列上创建,在多列上创建的叫做联合索引

    CREATE TABLE ddd(
        id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(50) NOT NULL,
        card_no VARCHAR(20) NOT NULL
    );
    #普通索引
    CREATE INDEX idx_name ON ddd(name); #联合索引 CREATE INDEX idx_name_cardno ON ddd(name, card_no);

      ③ 唯一索引:当表的某列被指定为 UNIQUE 约束时,则在此列上创建了一个唯一索引。

    CREATE TABLE ccc(
        id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(32) UNIQUE
    )
    
    #unique字段可以为NULL,并且可以有多个NULL
    CREATE TABLE ddd(
        id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(32) UNIQUE
    );
    
    CREATE UNIQUE INDEX name_uni ON ccc(name);
    
    #在普通索引创建时加上unique关键字即为唯一索引,唯一联合索引 

       ④ 全文索引:主要针对文件文本的检索,例如文章内容。全文索引只对MyISAM引擎起效。

    CREATE TABLE article(
         id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
         title VARCHAR(200) NOT NULL,
         body TEXT,
         FULLTEXT (title, body)    # 全文索引
    ) ENGINE=MyISAM CHARSET UTF8;
    
    #全文索引的使用
    SELECT * from article WHERE body LIKE '%mysql%';  #错误用法,不会使用全文索引 
    
    SELECT * FROM article WHERE MATCH(title, body) AGAINST ('mysql') # 正确用法
    
    #是否使用索引可以通过explain命令查看 

      注: a. 全文索引只对MyISAM引擎生效

         b. MySQL本身提供的全文索引只对英文生效

         c. 停止词:在文本中针对所有单词或字符创建索引是一个无穷大的数量级,因此对一些常用词和字符不会创建全文索引,这些词和字符被称作停止词

      2. 查询,删除,修改索引

    #查询
    desc table_name; #缺点:无法显示索引名称
    show index(es) from table_name;
    show keys from  table_name;
    
    #删除
    alter table table_name drop index index_name;
    
    #主键索引的删除(必须是非auto_increment)
    alter table table_name drop primary key;
    
    #修改:先删除,再添加
    alter table table_name drop index index_name;
    alter table table_name add index index_name table_name(col_name);
    View Code 

      3. 索引的代价

        a. 占用更多的磁盘空间(典型的以空间换时间)

        b. 使得DML语句变慢

      4. 创建索引的依据

        a. 创建索引的列经常在WHERE条件中使用

        b. 创建索引的表达到一定数据量,数据条数过少没必要创建索引

        c. 创建索引的列的字段值不是有限的几个值,例如性别,是否上线等

        d. 创建索引的列的字段值不是频繁变化,例如记录登录次数,在线人数等字段

      

     五. 分表技术

      当业务数据越来越多的时候,会导致某些数据表的数据量非常巨大,导致系统的性能下降。可以通过分表的方式改善性能。

      1. 水平分表

        将一张大表中的数据,或者即将产生大量数据的表,按照业务无关的属性随机均匀的存入多张结构相同的分表中。

        假设订单信息表,可以创建order_info_00,order_info_01...order_info_99,一共100张分表,订单编号是唯一的,每次存入的时候,用订单编号取hashcode后,再对100(分表数量)取模,得到的结果即为将要存储数据的分表的序号。

        Java中可如下操作: 

    #取哈希值时有可能结果为Integer.MIN_VALUE,导致取序号出错
    int index = Math.abs(orderNo.hashcode() % 100);  
    String idxStr = String.format("%2d", index);
    String tableName = "order_info_" + idxStr;

      

      2. 垂直分表

      在某些表中,可能会有占用空间比较大得字段,类型如text,varchar(3000),用于存储文章内容,回帖内容等,这些字段会严重影响系统的检索速度,此类字段查询的次数也相对较少,这个时候可以将其提取出来,单独建表存储,与原来的表共用主键id。这样在保证了数据的关联一致的同时,加快了原来表的检索速度。

      3. 数据库中文本视频类数据的存储

       通常不直接将文本或视频内容存储在数据库中,而只是存储文本或视频所在的路径,查询时按照路径去检索文件的真正内容。

      

     六. 读写分离

      当系统的并发访问量特别大的时候,单一的MySQL服务器的负载特别大,导致数据库性能下降,升值造成服务器崩溃。这个时候可以考虑搭建MySQL集群,使用读写分离技术来改善这种状况。集群中包含一台master服务器,多台slave服务器,master服务器负责执行DML(insert/delete/update)语句,slave服务器负载执行select语句,主服务器通过日志文件将操作同步到从服务器上。

       。。。。

     七. MySQL配置优化

       。。。。

     八. 定时维护

     1.存储引擎的选择

      MyISAM引擎:对事物要求不高,数据以添加和查询为主

      InnoDB引擎:严格要求失误,保存的数据都是重要数据

    MyISAM 和 InnoDB 区别
      事务支持 查询添加 全文索引 锁机制 外键支持
    MyISAM 支持 表级
    InnoDB 不支持 行级

      Memory 引擎:数据频繁变化,不需要入库,并且频繁查询修改,速度极快(MySQL服务重启后数据消失),可以用作缓存。

      2. MyISAM 引擎定期进行碎片整理

        使用 MyISAM 引擎的表中得数据被删除后,表数据存储所在的文件大小并不会改变,即表文件占用的空间不会释放,长此以往会严重拖慢表的查询速度。所以需要定期对使用 MyISAM 引擎的表进行碎片整理。

    > OPTIMIZE TABLE table_name;

      3. 定时备份

      ① 手动备份数据库

    #备份数据库,命令行下操作
    $ ./mysqldump -u root -pxxxx temp dept > ~/backend/mysql-temp-dept.bak
    #xxxx为 MySQL 登录密码,temp 为需要备份的数据库,dept 为需要备份的表名
    #表名可以有多个,[tb1, tb2, tb3,....]
    
    #使用备份内容恢复数据,MySQL 客户端下操作
    > source ~/backend/mysql-temp-dept.bak

      ②使用定时器完成自动备份

      a. Windows下

        1> 将备份指令写入一个bat脚本(mybaktask.bat): 

    #mysqldump.exe路径包含空格,需要用""包围
    "C:programe filesmysql5.6.26inmysqldump.exe" -u root -pxxxx temp dept >d:temp.dept.bak

        2> 把 mybaktask.bat 做成一个计划任务

         "计算机"-(右键)"属性"-"管理"-"系统工具"-"任务计划程序"-"创建任务"

        注:windows下希望每次备份的文件名称加上时间戳,不覆盖上一次备份的文件,可以写一个PHP脚本去执行备份命令,然后再定义一个定时任务去调用PHP脚本。  

      b. Linux / OSX 下

        1> 将备份指令写入 shell 脚本(mysql-bak-task.sh)    

    #!/bin/bash
    /usr/local/mysql/bin/mysqldump -u root -pxxxx temp dept > /home/byron/backend/mysql-temp-`date +%s`.bak

        2> 使用 contrab 创建定时任务

    $ sudo crontab -e  #编辑定时任务

    # 添加如下内容
    * 2 * * * /Users/byron/backEnd/mysql-bak-task.sh # 每天凌晨2点执行一次备份脚本

    九. 增量备份

      MySQL会以二进制的形式,自动将用户对MySQL数据库的操作 ,记录到文件中。    

  • 相关阅读:
    【转】ios输入框被键盘挡住的解决办法
    【转】操作系统Unix、Windows、Mac OS、Linux的故事
    mac 下删除非空文件夹
    解决Win7 64bit + VS2013 使用opencv时出现提“应用程序无法正常启动(0xc000007b)”错误
    图的邻接表表示
    图的邻接矩阵表示
    并查集
    05-树9 Huffman Codes及基本操作
    05-树7 堆中的路径
    堆的操作集
  • 原文地址:https://www.cnblogs.com/techroad4ca/p/5887043.html
Copyright © 2020-2023  润新知