大型网站核心优化之MySQL优化
复习
- 什么是三范式: 就是设计数据库的规则
- 三范式好处:减少冗余,加快读写效率
- 三范式有哪些:
- 每个字段不可以再分
- 一个表必须有主键、一个表只描述一件事
- 一个表不能包含其他表的非主键字段(一个表只能包含其他表的主键)
- 逆范式:以空间换时间
MyIsam和InnoDB存储引擎区别
- 存储结构:myisam三个文件、innodb2个文件
- 事物和外键支持:myisam都不支持,innodb都支持
- CURD操作:
myisam读快
innodb增、改快(注:如果需要删除整个表数据,直接用TRUNCATE语句删)
- 全文检索:mysql5.6+版本innodb和myisam都支持(都不支持中文)
- 锁:myisam支持表锁,innodb支持行和表锁
字段类型选择:略(重要)
#索引种类
普通索引(index) – 仅仅为加速
唯一索引(unique) – 保证唯一性
主键索引(primary key) – 既保证唯一性又不能为null
全文索引(fulltext) – 提取指定字段的内容增加索引
组合索引 – 给两个字段同时设置索引
#建表创建索引
create table 表名(
index(字段名),
unique(字段名),
primary key (字段名),
fulltext (字段名),
index(字段名1,字段名2)
);
#修改表创建索引
创建索引:alter table 表名 add 索引类型 索引名(字段名)
删除索引:drop index 索引名 on 表名
慢查询日志作用:记录慢SQL语句
如何开启:
show variables like ‘slow_query%’;
show variables like ‘%query_time’;
如何分析慢SQL语句:explain 待分析的SQL语句 \G
︴场景:并发
1、概念
场景:在实际工作中会出现负库存
原因:并发
并发:好比同时有n多人超市结账
负库存案例代码
1)SQL语句
create database shop;
use shop;
— —————————-
— Table structure for `goods`
— —————————-
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(11) NOT NULL DEFAULT ‘0’,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
— —————————-
— Records of goods
— —————————-
INSERT INTO `goods` VALUES (‘1’, ‘100’);
2)代码
// 语法:ab -n 1000 -c 100 http://localhost/test/test.php
// 说明:n – 请求总数 , c – 并且
//http://localhost/test/test.php
$conn = @mysql_connect(‘127.0.0.1′,’root’,’root’);
mysql_query(‘set names utf8’);
mysql_query(‘use shop’);
// $fp = fopen(‘lock.txt’, ‘w’);
// flock($fp, LOCK_EX);
mysql_query(‘lock table goods write;’);
$sql = “select num from goods where id = 1”;
$res = mysql_query($sql);
$tmp = mysql_fetch_assoc($res);
$num = $tmp[‘num’];
if ($num) {
//减库存
$sql=”update goods set num=num-1 where id = 1″;
mysql_query($sql);
}
// flock($fp, LOCK_UN);
mysql_query(‘unlock table;’);
echo ‘ok’;
die;
解决
MySQL锁(强烈不推荐)
<?php
// 语法:ab -n 1000 -c 100 http://localhost/test/test.php
// 说明:n – 请求总数 , c – 并且
//http://localhost/test/test.php
$conn = @mysql_connect(‘127.0.0.1′,’root’,’root’);
mysql_query(‘set names utf8’);
mysql_query(‘use shop’);
// mysql_query(‘lock table goods write’);//开启排它锁
$sql = “select num from goods where id = 1”;
$res = mysql_query($sql);
$tmp = mysql_fetch_assoc($res);
$num = $tmp[‘num’];
if ($num) {
//减库存
$sql=”update goods set num=num-1 where id = 1″;
mysql_query($sql);
// mysql_query(‘unlock table’);//释放锁
}
echo ‘ok’;
die;
2)文件锁(不推荐)
<?php
// 语法:ab -n 1000 -c 100 http://localhost/test/test.php
// 说明:n – 请求总数 , c – 并且
//http://localhost/test/test.php
$conn = @mysql_connect(‘127.0.0.1′,’root’,’root’);
mysql_query(‘set names utf8’);
mysql_query(‘use shop’);
// mysql_query(‘lock table goods write’);//开启排它锁
$fp = fopen(‘lock.txt’, ‘a+’);
flock($fp, LOCK_EX);
$sql = “select num from goods where id = 1”;
$res = mysql_query($sql);
$tmp = mysql_fetch_assoc($res);
$num = $tmp[‘num’];
if ($num) {
//减库存
$sql=”update goods set num=num-1 where id = 1″;
$rs = mysql_query($sql);
// mysql_query(‘unlock table’);//释放锁
flock(LOCK_UN);
}
echo ‘ok’;
die;
3)消息队列(推荐)
︴扩展:MySQL中锁机制【★★★】
概念
在MySQL数据库中MyISAM存储引擎采用的是表级锁,InnoDB存储引擎采用的是行级锁、也支表行级锁。(ps. 锁分两种模式,分别为:共享锁和排他锁)
共享读锁/共享锁(lock table 表名read;) 其他用户:读-可以,写-堵塞
独占写锁/排他锁(lock table 表名 write;) 其他用户:读-堵塞,写-堵塞
释放锁:unlock table
2、练习
1)表锁的演示
- MyISAM表锁
create table lock_t1 (
id int primary key auto_increment,
name varchar(30) not null
)engine=myisam charset=utf8;
#session1,开启共享锁
lock table lock_t1 read;
#session1,读(可以)
#session1,写(不可以,报错)
#session2,读(可以)
#session2,写(堵塞)
unlock table;
#session1,开启排他锁
lock table lock_t1 write;
#session1,读(可以)
#session1,写(可以)
#session2,读(堵塞)
#session2,写(堵塞)
unlock table;
- InnoDB表锁
create table lock_t2 (
id int primary key auto_increment,
name varchar(30) not null
)engine=innodb charset=utf8;
#session1,开启共享锁
lock table lock_t2 read;
#session1,读(可以)
#session1,写(不可以,报错)
#session2,读(可以)
#session2,写(堵塞)
unlock table;
#session1,开启排他锁
lock table lock_t2 write;
#session1,读(可以)
#session1,写(可以)
#session2,读(堵塞)
#session2,写(堵塞)
unlock table;
- 验证:MyISAM存储引擎默认是表级锁
上一讲我们插入了1千万数据不适用主键索引查询需要3~4min,问A窗口查询过程中B窗口修改等待还是直接执行?
分析:因为myisam是表锁,所以写堵塞
回答:堵塞
2)行锁的演示
create table t2 (
id int primary key auto_increment,
name varchar(30) not null
)engine=innodb charset=utf8;
insert into t2 value (null, ‘aaa’);
insert into t2 value (null, ‘aaa’);
begin;
update t2 set name = ‘bb’ where id = 1;
update t2 set name = ‘ccc’ where id = 1;
3、锁的特点
- 表级锁代表(MyISAM):开销小,加锁快;不会出现死锁;锁定粒度大,发生锁
冲突的概率最高,并发度最低(堵塞频率高)。
开销小:相对行级锁加锁资源较少
锁定颗粒度大:指锁的级别,最小的行级锁,最大的表锁
锁冲突高:因为针对整个表加锁,除非没有其他人操作否则冲突概率大
- 行级锁(InnoDB):开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突概
率最低,并发度也最高(堵塞频率低)。
死锁:指开启事物不提交导致
︴扩展:PHP中的文件锁机制【★★★】
1、说明
文件锁机制一般在单独操作文件时看不到效果,需要多人同时操作(并发)
2、文件锁的用途(举例)
若一个人在写入一个文件,另外一个人同时也打个了这个文件进行写入文件。
这情况下,很难明确谁的操作为准。因此,这个时候我们引入锁机制。若用户A在写入或者读取这个文件的时候,将文件加上共享锁。我可以读,其他人也可以读。但是,其他人不可以写。我使用独占锁/排它锁。这个文件归我了,你们都别动,除非我将文件锁进行释放。
A共享锁 B:读-可以,写-不行(堵塞)
A排它锁 B:读-不行(堵塞),写-不行(堵塞)
语法
- 语法:flock(资源,锁类型)
- 锁类型
LOCK_SH 取得共享锁定(读取的程序)
LOCK_EX 取得独占锁/排它锁定(写入的程序)
LOCK_UN 释放锁定(无论共享或独占)
LOCK_NB 当被锁定时,不阻塞,而是提示锁定。
r 读 ,指针文件头
r+ 读写,指针文件头(文件必须先存在)
w 写 ,清空数据,文件不存在自动创建
w+ 读写,清空数据,文件不存在自动创建
a 写 ,追加数据,文件不存在自动创建
a+ 读写,追加数据,文件不存在自动创建
脚下留心:加锁必须释放
浏览器机制:当前用户同一浏览器请求同一个文件,堵塞,所以使用两个文件测试
- 案例
a.php
<?php
header(‘content-type:text/html;charset=utf-8’);
/**
r 只读,并且光标行首
r+ 读写(注:文件必须存在)
w 写 (清空数据,文件不存在自动创建)
w+ 读写(清空数据,文件不存在自动创建)
a 写 (追加数据,文件不存在自动创建)
a+ 读写(追加数据,文件不存在自动创建)
*/
$fp = fopen(‘./a.txt’, ‘a+’);
/**
LOCK_SH 获取读锁(共享锁)
LOCK_EX 获取写锁(排他锁)
LOCK_UN 释放锁
LOCK_NB 当被锁定时,不阻塞,而是提示锁定。
*/
if (flock($fp, LOCK_EX)) {
echo fread($fp, 20);
sleep(15);
echo fread($fp, 20);
flock($fp, LOCK_UN);
}
b.php
<?php
$fp = fopen(‘./a.txt’, ‘a+’);
/**
LOCK_SH 获取读锁(共享锁)
LOCK_EX 获取写锁(排他锁)
LOCK_UN 释放所
LOCK_NB 当被锁定时,不阻塞,而是提示锁定。
*/
if (flock($fp, LOCK_EX | LOCK_NB)) {
echo 11;die;
// echo fread($fp, 20);
$rs = fwrite($fp, 9);
var_dump($rs);
flock($fp, LOCK_UN);
}
应用场景
秒杀项目,不推荐 -> 可用 php + redis 消息队列代替
第三方支付通知文件锁案例
一、前缀索引
1、什么是前缀索引
1)概念
前缀索引:指一个字段的内容,只需前面n个字符就可以具备唯一标识,将其创建为索引,最终形成“前缀索引”。
前缀索引好处:索引很长的字符列,它会使索引变大而且变慢,前缀索引能很好地减少索引的大小及提高速度。
举例
name
啊增
小泽
玛丽
苍苍
姚姚
发现:以前需要把整个name字段看完,才能找到数据,现在因为姓名第一个字符【啊、小…】具备唯一性所以我们只要第一个字符就可以快速查找。
2、制作前缀索引
1)创建数据
create table qianzhui (
id int primary key auto_increment comment ‘编号’,
name varchar(30) not null comment ‘姓名’
)engine=myisam charset=utf8;
insert into qianzhui values (null, ‘张三’);
insert into qianzhui values (null, ‘王八三’);
insert into qianzhui values (null, ‘小三’);
insert into qianzhui values (null, ‘大三’);
2)创建前缀索引
语法:alter table 表名 add index 索引名(字段名(字符个数))
alter table qianzhui add index qianzhui(name(1));
3)验证
二、全文索引(了解)
1、什么是全文索引
试想在1M大小的文件中搜索一个词,可能需要几秒,在100M的文件中可能需要几十秒,如果在更大的文件中搜索那么就需要更大的系统开销,这样的开销是不现实的。
全文索引:提取指定字段里面的内容添加索引
目的:加速like语句查询速度(注:mysql全文索引不支持中文) sphinx
2、具体操作
1)创建测试数据
#创建文章表
create table articles2 (
id int unsigned primary key auto_increment comment ‘编号’,
title varchar(80) not null comment ‘标题’,
content text not null comment ‘内容’
)engine=myisam charset=utf8;
#插入文章数据(禁中文,不支持)
insert into articles2
(title,content)
value
(‘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 …’);
添加全文所引
alter table articles2 add fulltext title(title);
使用全文索引
- 语法:select * from 表名where match(字段) against(‘内容’ IN BOOLEAN MODE);
- 参数:
- 如果添加全文索引是多个字段,如:fulltext(title,content)则查询语句字段处格
式:match(字段1,字段2)
- 如果需要检索多个关键字,则内容处:‘内容1,内容2’
三、limit分页优化(百万至千万级快速分页)
1、概念
当数据单表数据超过一定量后分页查询特别慢
分析
思路
1、从业务角度优化(百度、谷歌分页限制)
2、使用索引查询(where 条件 limit 数字) 索引+缓存(策略:功能+分页作为缓存唯一标识)
- 索引使用
发现:where通过主键赛选非常快,但是增加多个条件又慢了
解决:添加组合索引
五、查询缓存(了解)
1、概念
MySQL第一次执行后保存缓存数据,第二次直接从缓存中读取。
相关SQL语句
查看SQL缓存参数:show variables like ‘query_cache%’;
开启SQL缓存:set global query_cache_type = 1
关闭SQL缓存:set global query_cache_type = 0
设置缓存空间:set global query_cache_size = 1024*1024*64 #单位/M
固定SQL语句声明不适用缓存:select sql_no_cache * from 表名
开启缓存
步骤1:测试数据库
步骤2:开启缓存
set global query_cache_type = 1;
步骤3:设置缓存空间
步骤4:测试
4、不使用缓存
说明
发现:该功能了解即可,这是从MySQL层面缓存不推荐使用,因为及时缓存了PHP操作MySQL还是有额外开销
解决:后期通过php直接操作内存缓存(内存缓存 > 文件缓存 > 数据库)
六、分表技术(重点)
1、水平分表
1)说明
当一个表很大时我们创建索引后,查询速度还是很慢, 把一个巨大的表安装某个
标准分割成小表,这样可以提供查询的速度.
2)原理
2、垂直分表
- 目的:减少字段长度,加快查询速度。
- 表中存在多个字段:常用字段和非常用字段
- 例如:学生表可以分成,分为基础表和额外表,两张表中记录为1:1的关系。
原表
id name pwd age sex 籍贯 政治面貌 ……
垂直分割表
基础信息表(student_base)
id name age pwd
额外信息表(student_extra)
id 籍贯 政治面貌
3、为什么需要MySQL分表/分区
假设一个数据表中存储1亿条记录,我们检索数据就必须检索1亿条记录,但
是这样是非常耗时的,因为MySQL本质还是以文件形式存储的,所以其读取
都要涉及到服务器的I/O开销,这就是MySQL的性能瓶颈。遇到这种情况,我
们就可以使用分表程序,把1亿条记录的数据表分割成10份:每一份只需要存
储1000万条数据,这样其数据的检索也会变得更加高效!
分区算法
1)MySQL四种分区算法介绍
- 求余(主键字段):
- key 利用某个字段的值,进行求余,完成分区
- hash 利用某个整型表达式的值,完成分区
- 条件:
- list 值在指定列表内的分区
- range 值在某个范围内的分区
2)分区语法
说明:在创建表都得时候进行分区,后期也可以增加和删除分区
语法:
create table 表名(
)engine=存储引擎 charset=编号
partition by 分区算法(字段) 分区选项;
key分区
#创建数据库p1
create database p1;
#选择数据库
use p1;
#创建表
create table articles(
id int unsigned primary key auto_increment,
title varchar(50) not null,
content text
) engine = myisam charset = utf8
partition by key(id) partitions 10;
#根据key算法,创建10个分区(注:就相当于创建了10个表)
insert into articles values (null,’aaa’,’bb’);
flush table articles;
多学一招:flush table 表名; 刷新表清除缓存
4)hash 分区
#创建数据库p2
create database p2;
#选择数据库
use p2;
#创建表
create table articles(
id int unsigned primary key auto_increment,
title varchar(50) not null,
content text
) engine = myisam charset = utf8
partition by hash (id) partitions 4;
insert into articles values (null,’aaa’,’bb’);
flush table articles;
5)list分区
#创建数据库p3
create database p3;
#选择数据库
use p3;
#创建表
create table articles(
id int unsigned auto_increment,
title varchar(50) not null,
content text,
cid int unsigned,
primary key (id,cid)
) engine = myisam charset = utf8
partition by list(cid) (
partition c1 values in (1,3),
partition c2 values in (2,4),
partition c3 values in (5,6,7)
);
#创建3个分区,当插入cid=1或者3进c1, 当插入cid=2或4进c2分区
insert into articles values (null,’aaa’,’bb’, 1);
flush table articles;
5)range分区
#创建一个数据库
create database p4;
#选择数据库
use p4;
#创建数据表并实现range分区
create table user(
id int not null auto_increment,
name varchar(40) not null,
birthday date not null default ‘0000-00-00’,
primary key(id,birthday)
) engine = myisam default charset = utf8
partition by range(year(birthday)) (
partition 70hou values less than (1980),
partition 80hou values less than (1990),
partition 90hou values less than (2000),
partition 00hou values less than (2010)
);
insert into user values (null,’a’,’1970-01-01′);
insert into user values (null,’b’,’1980-01-01′);
insert into user values (null,’c’,’1990-01-01′);
# 有问题不符合条件
insert into user values (null,’d’,’2011-01-01′);
5、分区管理
1、概念
- 取余管理:增加分区-无影响,删除分区-无影响
- 条件管理:增加分区-无影响,删除分区-数据丢失
取余管理(key,hash)
增加分区数量:alter table 表名 add partition partitions 增加分区的数量
减少分区数量:alter table 表名 coalesce partition 减少分区的数量
3、条件管理(list,range)
删除分区:alter table 表名 drop partition 10hou;
添加分区
alter table tp_user add partition(
partition 10hou values less than (maxvalue)
);
alter table tp_goods add partition(
partition c4 values in (8,9,10)
);