********************************************************MYSQL数据库的优化技术***********************************************************************面试必须被问到
1:mysql数据库的优化技术
对Mysql的优化是一个综合性的技术,主要包括
1)对表的设计合理化(符合3NF3范式)
2)添加适当的索引(index):【常见的四种:普通索引,主键索引,唯一unique索引,全文索引,还有不常用的空间索引、符合索引】
3)分表技术(水平分割,垂直分割)
4)读写【写指的是update/delete/add】分离技术
5) 存储过程【请求--dbms数据库管理系统(编译过程费时间,我们可以预先编译好,再直接call)--数据库文件】[模块化编程,可以提高速度]
6)对mysql配置优化【配置最大并发数max_connections,一般网站调整到1000左右就ok了!!!在my.ini里面修改】【可以调整缓存大小】
7)mysql服务器硬件的升级
8)定时地去清除不需要的数据,并且要定时进行碎片整理(MyISAM)。
2:数据库表的设计
什么样的表才是符合三范式3NF?
表的范式是首先符合一范式,才能满足二范式,进一步满足三范式。
1NF:即,表的列具有原子性,不可再分割,即,列的信息不能分解了。只要数据库是关系型数据库(mysql oracle ,db2,informix , sysbase, sql server),就自动地满足1NF。
非关系型数据库(特点:面向对象或者是集合的)
nosql数据库:MongoDB(特点:面向文档)
2NF:表中的记录是唯一的,就满足2NF.通常,我们设计一个主键来实现。主键:不含业务逻辑,一般是自增长的。
3NF:表中不要有冗余数据。就是说,表的信息如果能够被推导出来,就不应该单独地设计一个字段来存放。
反3NF:
如相册中所有图片被访问的总次数会被放到相册表中,以直接看到访问次数。
在表的1对N情况下,为了提高效率,可能会在1这表中设计一些字段,提高速度。
3:sql语句本身的优化
问题是:如何从一个大项目中,迅速地定位执行速度比较慢的语句(定位慢查询)?
1)首先我们了解Mysql数据库的一些运行状态如何查询(比如想要知道Mysql运行的时间)
/一共执行了多少次select/update/delete/当前连接)
show status;
常用的:
show status like 'uptime'; //查询启动了多少时间
show status like 'com_select'; //com_insert 类推 update delete 查询select语句执行的次数
注意:show [session|global] status like …… 如果你不写【mession|global】,默认是session回话,只是取出当前窗口的执行,如果想要知道从Mysql启动到现在所有,需要加上global!!!!!!!!
show status like 'connections'; 可以知道有多少机器连接到Mysql数据库。
netstate -an;可以查到是哪一些连接到数据库。
linux下可以kill一个ip ,window下直接结束进程号即可。
show status like 'slow_queries';可以看到有多少次慢查询
2)如何去定位慢查询?
构建一个大表(400w条记录)-->存储过程构建
默认情况下,Mysql认为10秒才是一个慢查询。
*修改mysql的慢查询:show variables like 'long_query_time';//显示当前慢查询的时间
set long_query_time=1;//将默认慢查询时间设为1秒。
构建大表{压力测试脚本:构建的数据时不同的}--->大表中记录是有要求的,这样测试价值才高。
创建:
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
#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 $$
按照压力测试脚本写完即可。
select rand_string(6) my from dual$$ 从亚元表中取出数据。
select count(*) from emp$$ 可以看到条数
3)这里,如果我们出现一条语句,执行时间超过了1秒钟,就会被统计到。
4)如何将慢查询的sql语句记录到我们的一个日志中去?(默认情况下,我们的Mysql不会记录慢查询,需要在启动Mysql的时候指定记录慢查询才可以)
bin\mysqld.exe --safe-mode --show-query-log[mysql5.5可以在my.ini指定]
bin\mysqld.exe -log-slow-queries=d:/abc.log[低版本Mysql5.0可以在my.ini指定]
5)测试:可以看到日志中记录我们的mysql慢sql语句。
先关闭Mysql,再启动。如果启用了慢查询日志,默认将这个文件my.ini文件中记录的位置
#path to the database root
datadir="路径"
优化问题:
通过explain语句可以分析,mysql如何执行你的sql语句。(mysql参考手册里面的)这个工具的使用,我们先放一放,一会儿说。
4:添加索引
四种索引(主键索引、唯一索引、全文索引、普通索引)
1)添加索引
当一张表,将某一个趔设为主键的时候,则该列就是主键索引
create table aaa
(id int unsigned primary key auto_increment ,
name varchar(32) not null default '');
这时,id主键就是索引
如果你创建表时,没有指定主键索引,也可以在创建表后,再添加,指令:
alter table 表名 add primary key (列名);
举例:
create table bbb(id int,name varchar(32) not null default '');
alter table bbb add primary key (id);
1.2)普通索引
一般来说,普通索引的创建是先创建表,然后再创建普通索引
比如:
create table ccc(
id int unsigned,
name varchar(32)
);
create index 索引名 on 表 (列);
1.3)创建全文索引
:全文索引主要是针对文字的,文本的检索,比如文章。全文索引针对MyISAM有用!!!!!!
create table articles(
id int unsigned auto_increment not null primary key,
title varchar(200),
……
)engine=myisam charset utf-8;
如何使用全文索引?
错误用法:select * from articles where body like '%mysql%';【不会使用到全文索引】
证明:explain select * from articles where body like '%mysql%'\G
正确用法:select * from articles 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 auto_increment ,name varchar(32) unique);
这时,name列就是一个唯一索引。
unique可以为null,并且可以有多个;但是如果是具体的内容,即 ' ',则不能重复。
主键字段,不能为null,也不能重复。
创建唯一索引的第二种创建方式:
当在创建表后,再去创建唯一索引。
create table eee(id int primary auto_increment,name varchar(32));
create unique index 索引名 on 表名 (列名);
2)查询索引
desc 表名; 【缺点:它不能将索引的名字体现出来】
show index(es) from 表名\G;
show keys from aaa\G;
3)删除索引
alter table 表名 drop index 索引名;
如果删除的是主键索引,还可以这样:
alter table 表名 drop primary key ;[这里有一个小问题]
4)修改索引
一般做法:先删除,再重新创建。
为什么创建索引后,查询速度会变快?
全部都要检查 ----> 二叉树(BTREE)
索引使用的注意事项:
1)索引的代价:一是会导致磁盘占用,二是对dml(update delete insert )语句的效率会变慢
2)在哪些列上适合添加索引?
1)在较为频繁地作为查询条件的字段
2)总结:满足一下条件的字段才应该创建索引:肯定在where条件里经常使用,该字段的内容不是唯一的几个值(性别等),字段内容不频繁变化的。
使用索引的注意事项:(';\c 退出mysql语句的书写
把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 8 from dept where dname like '%aaa'\G 就不能使用索引
即,在Like查询时,关键的‘关键字’最前面不能使用 % 或者_ 这样的字符。如果一定要前面有变化的值,则考虑使用全文索引------------------------>sphinx。
explain 可以帮助我们在不真正执行某一个sql语句时,就执行mysql怎么样执行,这样利用我们我们去分析sql指令
3)如果条件中有or,即使条件带索引,也不会使用。换言之,就是要求使用的所有字段都必须建索引。我们建议大家,尽量避免使用or关键字
4)如果列类型是字符串,需要在条件中使用引号引起来。即,如果有列是字符串类型,就一定要用单引号包起来。
5)如果mysql估计使用全表扫描比使用索引块,则不用索引
5:优化诀窍
查看索引的使用情况:show status like 'Handler_read%';这个值越高,说明这个索引使用率越高。 handler_read_key越高越好 handler_read_rnd_next越低越好
sql语句的小技巧
优化group by语句:在使用group by分组查询后,默认分组后,还会排序,可能会降低速度。select * from emp(表名) group by deptno(部门编号) ;
我只想分类并不像排序,就这样写:select * from emp(表名) group by deptno(部门编号) order by null;即可。
有些情况下,使用连接来替代子查询。因为使用join,Mysql不需要在内存中创建临时表
select * from empp where dept,deptno=emp,deptno;不该被使用[简单处理方式]
select * from dept left join emp on dept.deptno=emp.deptno;【左外连接,更ok!!!】
如何选择mysql的存储引擎???
myisam存储引擎:如果表对事物要求不高,同时是以查询与添加为主的,我们考虑使用myisam存储引擎。比如bbs中的发帖表,回复表。
INNODB存储:对事物要求高,保存的数据都是重要数据,我们建议使用 INNODB,比如订单表,账户表
问:MYISAM与INNODB的区别:
1)事物安全
2)查询与添加速度
3)支持全文索引方面
4)锁机制
5)外键:MyISAM不支持外键(在PHP开发中,通常不设置外键,通常是在程序中保证数据的一致)
memory存储:比如我们数据变化非常频繁,不需要入库,同时又频繁地查询与修改,我们考虑使用Memory引擎。
速度极快!!!!!在内存里,不在磁盘里。重启mysql后全部丢失。
如果你的数据库引擎是myisam,请一定记住,要定时进行碎片整理
举例说明:
create table test100(id int unsigned,name varchar(32)) engine=myisam;
insert into test100 value(1,'aaaaa');
insert into test100 value(1,'bbbbb');
insert into test100 value(1,'ccccc');
insert into test100 select id,name from test100;蠕虫复制!!!!!!!!!!!!!!!!!!!!!!!!!!!1-2--4---8---16
对于这样的情况,我们应该对myisam进行整理
optimize table test100; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
mysql_query("optimize table $表名"); //循环执行
技术就是窗户纸。------->经常与技术好的人交流。
5:PHP定时完成数据库的备份。
1)手动备份数据库(表的)方法
mysqldump -u root -proot 数据库名 [表名1 表名2] >emp.bak 文件路径 【在cmd控制台】
比如:将temp数据库备份到d:\temp.bak
mysqldump -u root -proot temp >d:\temp.bak
如果你希望备份的是数据库的某几张表
mysqldump -u root -proot temp dept >d:\dept.bak
如:
cd c;\myenv\mysql5.5.27\bin
mysqldump -u root -proot temp dept >d:\temp.dept.bak
如何使用备份文件恢复我们的数据?
source d:\temp.dept.bak; 【在Mysql控制台】
2)我们使用定时器自动完成
思路:将备份数据库的指令写入到.bat批处理文件,然后通过任务管理器(windows下)或者crontab(linux下)定时【定时器】管理文件
mytask.bat内容:
C:\myenv\mysql5.5.27\bin\mysqldump -u root -proot temp dept >d:\temp.dept.bak
或者[如果你的mysqldump.exe文件路径有空格,一定要用双引号包起来]
"C:\myenv aaa\mysql5.5.27\bin\mysqldump" -u root -proot temp dept >d:\temp.dept.bak
将mytask.bat做成一个任务,并定时调用,一般来说是在凌晨2:00调用一次。
控制面板 添加任务计划
步骤:任务计划-增加一个任务,选中你的mytask.bat文件,最后配置时间。
现在的问题是:每次都是覆盖原来的备份文件,不易于我们分时间段进行备份[让.bat文件操作php文件]。
mytask.php
<?php
//定时备份我们的数据库文件
date_default_timezone_set('PRC');
$bakfilename=date("YmdHis".time());
$command=" C:\myenv\mysql5.5.27\bin\mysqldump -u root -proot temp dept >d:\\{$bakfileanme}";
exec($command);
?>
思路:
cd c:\myenv\php5.5.27
php.exe c:\apache……mytask.php //在cmd下用php.exe文件执行
mytask2.bat
php.exe的绝对路径 C:\myenv\apache\htdocs\mytask.php
再在计划任务里完成设置即可。
6:PHP定时完成发送邮件发送功能
1)看一个实际的需求:清华校友邮箱里的定时发送
2)设计一张邮件表
create table maillist
(id int unsigned primary key auto_icrement,
getter varchar(64) not null default '',
sender varchar(64) not null default '',
title varchar(32) not null default '',
content varchar(2048) not null default '',
sendtime int unsigned not null default 0,
flag tinyint unsigned not null default 0)engine=myisam charset utf8;
添加两条测试数据进去
insert into maillist value(null,'[email protected]','[email protected]','hello100','abc hello100',unix_timestamp()+10*3600,0);
insert into maillist value(null,'[email protected]','[email protected]','hello200','abc hello200',unix_timestamp()+10*3600,0);
3)写代码
1:怎么样可以定时地去检索哪些邮件该发送?只能每隔一定时间(1min)就看看哪一些邮件该发送
mailtask.php
<?php
//连接数据库(mvc),这里我把代码直接写在这个文件
$con=mysql_connect("localhost","root","root");
if(!$con){
die("连接失败");
}
mysql_select_db("temp",$con);
$sql="select * from maillist where flag=0";
$res=mysql_query($sql,$con);
//遍历
while($row=mysql_fetch_assoc($res)){
//取出该邮件的时间
$sendtime=$row['sendtime'];
/如果到时间了,就发送
if($sendtime<=time()){
echo '<br/>'.$row['id'].'号邮件发送……';
//如果发送成功
$sql="update maillist set flag=1 where id={$row['id']}";
mysql_query($sql,$con);
}
}
mysql_free_result($res);
mysql_close($con);
?>
insert into maillist value(null,'[email protected]','[email protected]','hello300','abc hello200',unix_timestamp()+20,0);
上面的代码是模拟发送邮件。
2)如何真实发送邮件?
在PHP中有一个mali函数,网上有一个开源的类,我们可以拿来直接用:PHPMailer类
***要正确地使用邮件发送PHPMailer,需要满足如下条件:
a)本身这台机器是联网的
b)需要搭建自己的smtp邮件服务器【smtp/pop3两个协议在跑,可以收发邮件】
3)搭建自己的邮件服务器(网上有很多种,我们这里使用dbmailsetuppro.exe遥志邮件服务器)
卸载原先的。
安装:傻瓜式安装。
配置:
选择microsoft access数据库
去掉webmail的对勾
配置邮箱服务器
点击创建邮箱域名:以自己的ip为域名
划掉自动创建账号的三个对勾。
点击进行服务器设置:网络里面去除本地交互。邮件中下次重发间隔改为1min。
设置一个账号(使用版本只能设置5个账号):新建账号 全部去掉什么允许的对勾(5个)采取默认的也可以。
在PHPMailer类中的一个叫做mysendmail.php文件中做一些修改:$mailer->From='[email protected]';等
配置php.ini,启用账号[email protected]
测试一把:重启apache
7:对表进行水平分割技术
当一个表海量数据,怎么办?举例
根据刚才的分析,我们模拟一下
1,添加用户(addUser.php)
2,验证用户(checkUser.php)
create table 11login0(id int primary key,name varchar(32),pw char(32));
create table 11login1(id int primary key,name varchar(32),pw char(32));
create table 11login2(id int primary key,name varchar(32),pw char(32));
开发addUser.php 因为在添加用户时,用户的id应该确认下,通常,我们可以使用一个辅助表uuid表,它可以帮助我们生成一个编号
create table uuid(id int unsigned primary key auto_increment );
addUser.php
<?php
$conn=mysql_connect("localhost","root","root");
if(!$conn){
die("连接失败");
}
mysql_select_db('newsdb');
//获取一个uuid-->就是qq号,如果算法不一样,生产的唯一值,是自增的,可以找到其它算法。
$qqid=intval($_GET['id']);
$tablename='qqlogin'.$qqid%3;
$sql='select count(*) nums from '.$tablename."where id=$qqid";
$res=mysql_query($sql,$conn);
if($row=mysql_fetch_assoc($res)){
echo '<br/>在'.$tablename.'有这个id对应的用户'.$row['id']
}else{
die("没有用户");
}
?>
checkUser.php
<?php
$conn=mysql_connect("localhost","root","root");
if(!$conn){
die("连接失败");
}
mysql_select_db('newsdb');
//获取一个uuid-->就是qq号,如果算法不一样,生产的唯一值,是自增的,可以找到其它算法。
$sql="insert into uuid values(null)";
if(mysql_query($sql,$conn)){
$uuid=mysql_insert_id();
$tablename='qqlogin'.$uuid%3;
$sql='inseert into'.$tablename."values ('$uuid','aaa','aaa')";
if(mysql_query($sql,$conn)){
echo "添加用户成功";
}else{
die("添加失败");
}
代码不全,参考韩顺平上课笔记。!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
?>
分享一句:我们在提供检索时,应该业务的需求,找到分表的标准,并在检索页面,约束用户检索的方式。----->配合分页
8:垂直分割
将某个表的某些字段,这些字段在查询时并不是经常关心的,但是数据量又很大,我们建议将这些字段单独地放到另外一张表中,从而提高效率。
表的字段定义是保小不保大,尽量节省空间
建议使用一个不含业务逻辑的,为主键
tinyint<smallint<mediumint<int<longint
关于网站的图片与视频
我们的数据库表中,一般只是存放路径,资源放在文件系统(往往配合独立的服务器)
9:如何优化我们的mysql配置
修改myphp.ini
端口号
最大连接数max_connection=10000等【并发数只有2000】
关于缓存query_cache_size= 调大一些。
对innodb引擎:innodb_addtional_mem_pool_size
innodb_buffer_pool_size= 调大
对myisam:key_buffer_size
如果你的机器内存大于4G,该换64位系统,64位的mysql
10:读写分离
数据库压力大,一台机器支持不下去,使用mysql复制实现多台机器同步,将压力分散。
1:我们补充讲解一下Mysql的增量备份。
增量备份定义:mysql数据库会以二进制的形式自动地将用户对mysql数据库的操作记录到文件。
当用户希望恢复的时候,可以使用备份文件进行恢复。
注意:增量备份会记录(dml语句,创建表的语句,不会记录seelct语句)
它记录的(操作语句本身,操作的时间,创建数据库,删除数据库,操作的position位置)
原理图:
实际案例--->如何进行增量备份与恢复。
第一步:配置一个my.ini文件,或者是my.conf,启用二进制备份(增量备份)
在pory=3307的下一行写上指定将备份文件放在哪儿:log-bin=d:/binlog/mylog (my.ini)
第二步:启动Mysql(得到文件d:/binlog/mylog/mylog.index索引文件,有哪一些增量备份文件 d:/binlog/mylog/mylog.000001 存放你用户对数据库操作的文件)
补充:可以使用mysql/bin/mysqlbinlog程序来查看备份文件的内容(进入到cmd,mysqlbinlog 备份文件的绝对路径;)
第三步:当我们进行操作(除了select)都会被记录下来。根据时间或位置来恢复。
时间点:
mysqlbinlog --stop-datetime="2013-01-14 18-20-20" d:/bin/shunping.000001 | mysql -uroot -p
位置点
mysqlbinlog --stop-position="110(位置点)" d:/bin/shunping.000001 | mysql -uroot -p
取出时间段的操作
mysqlbinlog --start-datetime="2013-01-13 18-20-20" --stop-datetime="2013-01-14 18-20-20" d:/bin/shunping.000001 | mysql -uroot -p
取出位置段的操作
mysqlbinlog --start-position="100(位置点)" --stop-position="110(位置点)" d:/bin/shunping.000001 | mysql -uroot -p
如何在工作中将全备份与增量备份配合使用???
方案:每周一做一个全备份mysqldump,启用增量备份,将过期时间设为大于等于7.
如果出现数据库崩溃,就可以通过时间或者位置来恢复。(你需要去看源代码,分析增量日志)