MySQL优化技术

策略概述

对MySQL优化是一个综合性的技术,主要包括一下几个方面:

a. 表的设计的合理化 [ 符合3NF ]

b. 添加适当的索引(index)[ 普通索引 主键索引 唯一索引 全文索引 ]

c. 分表技术 [ 水平分割 垂直分割 ]

d. 读写分离 [ select add/update/delete ]

e. 存储过程 [ 模块化编程 可以提高速度 ]

f. 对mysql配置优化(my.ini) [ 配置最大并发数 调整缓存大小 ]

g. 对mysql服务器硬件升级

h. 定时进行碎片整理 [ 尤其是MyISAM引擎 ]

数据库分类

关系型数据库: mysql/oracle/db2/informix/sysbase/sql server

特点: 面向对象或集合

非关系型数据库: mongodb/redis

特点: 面向文档

三范式标准

合理的表设计应遵循三大范式,即3NF。首先要符合1NF,才能满足2NF,进一步3NF。

1NF:即表的列具有原子性,不可再分解,即列的信息不能再分解,只要数据库是关系型数据库,就自动满足1NF。

2NF:表中的记录是唯一的,就满足2NF,通常我们设计一个主键来实现。主键不含业务逻辑,一般是自增的,数据比较稳定。

3NF:即表中不要有冗余数据,就是说,表的信息如果能够被推导出来,就不应该单独设计一个字段来存放。

比如下面的设计就不满足3NF:

反3NF:没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低方式标准,适当保留冗余数据。

比如下面的设计就要反3NF:

具体的做法是,在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑,降低范式就是增加字段,允许冗余。

SQL语句优化

背景问题: 如何在一个大项目中迅速的定位执行速度比较慢的语句,即定位慢查询。

1.查询mysql运行状态

show status 命令

show status like 'uptime';           //查询当前mysql运行时间

show status like 'com_select';       //查询select命令执行次数(add/update/delete类推)

show [session|global] status like ...//如果不写[session|global]默认是session会话,取出当前窗口命令执行情况,如果你想看所有命令执行汇总,则应指定global

show status like 'connections';      //查询mysql连接数

show status like 'slow_queries';     //查询慢查询次数

2.定位慢查询准备工作

这里为了模仿应用场景,我们使用存储过程构建一个大表,表中有4000w条记录,并且这些记录应不同,否则测试效果和实际情况差距很大。默认情况下,mysql认为10s才是一个慢查询,因此我们需要修改mysql慢查询规定的标准时间。

show variables like 'long_query_time';    //查询当前慢查询规定时间

set long_query_time = 1;                  //修改慢查询规定时间,这时如果语句执行超过1s就会被统计到 

创建大表的SQL代码如下:

/*部门表*/
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 ;

/*雇员表*/
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 $$

//创建自定义函数:
create function rand_string(n INT) 
returns varchar(255) #该函数会返回一个字符串
begin 
#char_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 $$

create function rand_num( )
   returns int(5)
begin 
   declare i int default 0;
   set i = floor(10+rand()*500);
return i;
end $$

//创建存储过程
create procedure insert_emp(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 emp values ((start+i),rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
    until i = max_num
end repeat;
commit;
end $$

//调用存储过程
call insert_emp(100001,4000000);

MySQL中可以保存的内容有:表,视图,函数,过程,触发器。

MySQL中除了concat()等内置函数以外还可以定义自己的自定义函数,自定义函数也可以在php脚本中调用:

<?php
$conn = mysql_connect("localhost", "root", "root");
if(!$conn)
    die("失败");
mysql_select_db("temp");
//dual亚元表,单纯为了使用函数,不与表关联
$sql = "select temp.rand_string(6) my from temp.salgrade";
$res = mysql_query($sql, $conn);
if($row = mysql_fetch_assoc($res))
    echo $row['my'];
?>

3.将慢查询SQL语句记录到日志中

默认情况下,MySQL不会记录慢查询,需要在启动MySQL的时候指定慢查询。

bin/mysql.exe --safe-mode --slow-query-log [mysql5.5可以在my.ini配置文件中指定]
bin/mysql.exe -log-slow-queries=d:/abc.log [低版本mysql5.0可以在my.ini配置文件中指定]

先关闭MySQL再重新启动,如果启动慢查询日志,默认把这个文件放在my.ini文件中记录的位置。

#Path to the database root
datadir="C:/Documents and Settings/All Users/Application Data/MYSQL 5.5/Data"

索引往往和物理存储相关联,数据库中数据迁移的时候应注意这一点。

运行在安全模式下的MySQL会将一些操作记录日志,具备一定的恢复能力。

通过测试,我们可以看到在日志中记录下了MySQL的慢查询语句及其相关信息。

4.针对慢SQL语句的优化方案

通过explain语句分析mysql数据库如何执行sql语句,explain可以帮助我们在不真正执行某个sql语句时,显示mysql会怎样执行该条sql语句,从而帮助我们分析语句执行情况。

//explain分析sql的回显结果说明

id : 查询序列号

select_type:查询类型

table:查询标名

type:扫描方式(all表示全表扫描)

possible_keys:可能使用到的索引

key:实际使用的索引

rows:该sql语句扫描了多少行,可能得到多少记录

Extra:sql语句的额外信息,比如排序方式

索引的使用

  1. 添加索引
    1.1 主键索引
    当把一张表的某个列设为主键的时候,则该列就是主键索引:

    create table aaa(
    id int unsigned primary key auto_increment,            //这个id列就是主键索引
    name varchar(32) not null default '' 
    );
    

    如果创建表时没有指定主键索引,可以在创建表后添加:

    create table bbb (id int , name varchar(32) not null default '');
    alter table bbb add primary key (id);
    

    1.2 普通索引
    一般来说,通常是先创建表,再创建普通索引

    create table ccc(
    id in unsigned,
    name varchar(32)
    );
    create index 索引名 on 表 (列);
    

    1.3 全文索引

    全文索引主要是针对文本的检索,比如文章等,只有MYISAM存储引擎支持全文索引。

    全文索引的添加

    create table article(
    id int unsigned auto_increment not null primary key,
    title varchar(200),
    body text,
    fulltext(title,body)
    )engine=myisam charset utf8;
    

    全文索引的使用

    select * from article where body like '%mysql%';     [不会使用全文索引]
    explain select * from article where body like "%mysql%";      [通过分析我们证明了不会使用全文索引]
    select * from article where match(title, body) against('database');  [会使用全文索引]
    

    全文索引的说明

    1. 在MySQL中全文索引(fulltext)只针对MYISAM存储引擎有效。

    2. MySQL自己提供的全文索引(fulltext)只针对英文有效,针对中文有sphinx(coreseek)解决方案。

    3. 全文索引的使用方法 match (字段名) against ('关键字');

    4. 全文索引的停止词:在一个文本中创建索引是一个无穷大的数,因此,对于一些常用的词和字符就不会创建,这些词成为停止词。

    1.4 唯一索引

    当表的某列被指定为unique约束时,这列就是一个唯一索引。

    创建表时创建唯一索引

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

    创建表后创建唯一索引

    create table eee(id int primary key  auto_increment, name varchar(32));
    create unique index 索引名 on 表名 (列名)
    

    唯一索引的字段可以为空,为空时可以重复,但是不为空时则不能重复。

    主键索引的字段不可以为空,也不可以重复。

  2. 查询索引

    desc 表名 [该方法的缺点是不能够显示索引名]
    
    show index(es) from 表名;
    
    show keys from 表名;
    
  3. 删除索引

    alter table 表名 drop index 索引名;  
    
    alter table 表名 drop primary key;         //删除主键索引
    
  4. 修改索引

    先删除,再创建。

索引的原理(二叉树算法BTREE)

索引的注意事项

  1. 索引的代价

    磁盘占用

    对dml语句(update delete insert)的效率影响,由于维护索引文件,会使其变慢。

  2. 索引的使用

    a. 较为频繁的作为查询条件字段应该创建索引

    select * from emp where empno=1;
    

    b. 唯一性太差的字段即便频繁作为查询条件也不适合单独创建索引

    select * from emp where sex='男';
    

    c. 更新非常频繁的字段不适合创建索引

    select * from emp where logincount=1;
    

    d. 不会出现在where子句中的字段不该创建索引

总结起来满足如下条件的字段才应该创建索引

  1. 肯定在where条件经常使用。
  2. 该字段的内容不是唯一的几个值。
  3. 字段内容不是经常变化的。
  1. 索引注意事项

    alter table dept add index my_ind(dname, loc);  //dname是左边的列,loc是右边的列  
    

    如果我们的表中有复合索引(索引作用在多列上),此时我们应注意:

    1. 对于创建的多列索引,只要查询条件使用了最左边的列,索引一般就会被使用

      explain select * from dept where dname='aaa'\G   //会用到索引 
      
      explain select * from dept where loc='xxx'\G     //不会用到索引
      
    2. 对于like的查询,查询如果是'%aaa',不会使用索引,如果是'aaa%',会使用索引

      explain select * from dept where dname like '%aaa'\G   //不会使用索引
      
      explain select * from dept where dname like 'aaa%'\G   //会使用索引
      

    在like查询时关键字的最前面不能使用'%'或'_'这样的字符,如果一定要在前面有变化的值则考虑使用全文索引或sphinx

    1. 如果条件中有or,即使其中有条件带索引也不会使用。换言之,就是要求使用的所有字段都必须建立索引。

    我们建议大家尽量避免使用or关键字

    1. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不能使用到索引。(数值类型会转化为字符串类型)

    2. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引。

  2. 索引使用查询

    show status like 'Handle_read%';
    
    handler_read_key  这个值越高表示使用索引查询到的次数越多
    
    handler_read_md_next  这个值越高说明查询效率越低
    
  3. 查询语句技巧

    1. 在使用group by分组查询时,默认分组后还会排序,可能会降低速度

      select * from dept group by depname\G           //默认会排序
      
      select * from dept group by depname order by null\G     //避免了排序
      
    2. 有些情况下可以使用连接来代替子查询,因为使用join时MySQL不需要在内存中创建临时表

      select * from dept, emp where dept.deptno=emp.deptno;   //简单处理方式
      
      select * from dept left join emp on dept.deptno=emp.deptno; //使用左外连接更好 
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容