一、关系型数据库在项目中通常会遇到哪些问题或者说是瓶颈呢??
这里简单的举例如下:
-
高并发读写操作数据库,比如我们的双十一购物场景 --> 解决:集群、分布式
-
海量数据的高效率读写 ,比如在一张包含海量数据的表中查询,插入速度会很慢(ex:淘宝,京东等商城项目中的商品表) --> 解决:分表,分库
-
高扩展性和可用性
在基于web的结构(即浏览器/服务器)当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库却没有办法简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展 是非常痛苦的事情,往往需要停机维护和数据迁移. 也就是说我们:动态的去添加服务器,一台数据库服务器扩充到多台时,不下电情况下是很难做到的。 单点故障:一台数据库服务器挂了。业务就中断,期望去找还好的数据库继续提供服务。
二、那么关系型数据库如何优化??
对关系型数据库的优化是一个综合性的技术,主要包括
1、找出执行效率低Sql(定位慢查询) -发现问题
2、分析慢Sql -分析问题
3、优化 -解决问题
3.1 单机(表结构、索引、Sql(代码问题)):
表
合适表结构 3NF和反3NF
合适引擎
索引
分表(垂直分表和水平分表)
分区(了解)
Sql优化技巧
3.2 多机(IO瓶颈):
读写分离(集群,让多台服务器提供服务)
分布式(把不同的业务分给不同的集群处理)
3.3 缓存方案
1、优化方案不仅可以适用在mysql,还可以使用于oracle等关系型数据库,只是命令不同罢了。
2、优化时先考虑单台数据库服务器,如果单台优化完成后,实在满足不了需求再考虑多机集群和分布式。(有的公司不会使用多台数据库)
1、定位慢查询 - 找出执行慢的sql
1.1 原理:
当我们使用数据库,实质上就是连接数据库,发送SQL、服务器执行SQL并返回结果、关闭连接。这期间所有的sql都是mysql执行,当它发现客户端执行sql慢的时候,会把结果记录下来。方便用户查找定定位。
1.2.1 查看数据库基本状态: (比如想知道当前mysql运行的时间/一共执行了多少次select/update/delete… / 当前连接数/最大连接数等)
-- 1.查看数据库运行时间(单位:秒)
show status like 'uptime'
-- 2.查看数据库crud次数(作为选择存储引擎的依据)
show status like '%Com_%'
# crud:
show status like '%Com_select%'
show status like '%Com_insert%'
show status like '%Com_update%'
show status like '%Com_delete%'
Show session/global status like '%Com_select%'
☞ show [session|global] status like .... 如果你不写[session|global] 默认是session 会话(指取出当前窗口的执行),如果你想看所有(从mysql 启动到现在),则应该 global。
INNODB
show status like '%Innodb_rows%';
-- 3.查看所有连接数
show status like 'connections'
-- 4.查看服务器最大连接数(可根据此设置数据库最大并发连接数的依据,一般 mysql的最大连接数默认是100, 最大可以达到16384(理论上))
show status like 'max_used_connections'
1.2.2 定位慢查询
-- 1.查看慢查询,通常指花了2S以上的查询(默认10s)
show status like 'slow_queries'
-- 2.查看当前服务器慢查询的定义时间
show variables like 'long_query_time'
-- 3.修改服务器慢查询时间(注意:默认不写set是session,即在当前的的窗口中才有效果,如果关闭窗口后打开新窗口有效果 则加上global关键字)
set long_query_time=0.5
set global long_query_time=0.5
-- 4.将慢查询记录到日志中
#mysql安装的bin目录下执行
mysqld.exe --safe-mode --slow-query-log #[mysql5.5版本 可以在my.ini指定]
注意事项:
- 必须在mysql的安装目录执行,因为执行的使用依赖于my.ini,
- 慢查询日志会输出到data.dir
- 以下文件不要乱删,乱修改,会导致mysql没法启动
1.2.3 步骤总结:
1)关闭原有mysql服务
2)以支持记录慢sql的方式来启动
3)设置慢查询时间阀值
4)构造慢sql
5)到日志文件中找慢sql(data.dir)
什么时候开启慢查询? - ①②
系统中所有sql都执行一遍,才能判断是否有慢sql。什么时候开启能覆盖所有sql执行?
①开发者自验:开发完成后,需要统一打包,统一部署,统一验证。
②测试人员测试:测试人员需要测试所有功能。
③项目上线:用户用了所有功能。
2、分析慢查询 - 分析SQL语句
explain+sql语句 -》用于分析sql (主要看:type如果为all 表示全表扫描 possible_keys 可能用到的索引 keys 实际使用的索引 rows扫描的行数)
末尾加\G 表示格式化
3、单机优化
3.1 表结构设计
3.1.1 范式(规范)- 常见有三种规范 3NF
1NF:原子性 列不可分割
2NF:记录具有唯一标识 通常通过主键实现
3NF:表中尽量不要有冗余数据,能够通过其他表推导出来的数据不要单独设置字段
---
反3NF:没有冗余的数据库表未必是最好的数据库表,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。具体做法是: 在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,允许冗余。
ex:单价和总价
3.1.2 存储引擎
分类 - mysql中 : myisam,innodb,memory
优缺点:
MyISAM 和 INNODB的区别:
1.事务安全 --- MyISAM不支持事务,INNODB支持
2.查询和添加速度 --- MyISAM速度快,INNODB速度慢
3.支持全文索引 --- MyISAM支持,INNODB不支持
4.锁机制 --- MyISAM表锁 INNODB行锁
5.外键 --- MyISAM:不支持外键, INNODB:支持外键. (通常不设置外键,通常是在程序中保证数据的一致)
使用场景:
MyISAM存储引擎:如果表对事务要求不高,同时是以查询和添加为主的,我们考虑使用myisam存储引擎. 比如 bbs 中的 发帖表,回复表.
INNODB存储引擎: 对事务要求高,保存的数据都是重要数据,我们建议使用INNODB,比如订单表,账号表.
Memory存储:比如我们数据变化频繁,不需要入库,同时又频繁的查询和修改,我们考虑使用memory, 速度极快.
操作:
创建表时指定存储引擎:
Create table 表名(字段列表) engine 存储引擎名称;
注意:如果不指定则使用默认的存储引擎,这个默认在my.ini配置
修改存储引擎:
alter table table_name engine=innodb;
3.2 索引
3.2.1 引入
说起提高数据库性能,索引是最物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行个正确的‘create index’,查询速度就可能提高百倍千倍,美滋滋。可是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的I/O。
3.2.2 索引是什么?
索引是帮助DBMS高效获取数据的一种数据结构
原理:以空间换时间
组织数据方式,及访问数据的api的封装。-list,set,map数组
使用索引 查询速度提高 但以插入、更新、删除的速度为代价!!
索引算法:
MySQL常用引擎允许的索引类型
FullText全文索引算法,myisam只能能在char vachar text
hash就像Map,通过一个key直接就能找到value
B-tree算法
总结:使用索引把全表查找变为索引查找,减少查询次数,增加查询效率。而索引查找效率的取决于索引算法。也就是索引(Index)是帮助DBMS高效获取数据的数据结构
3.2.3 操作
mysql中索引的分类:普通索引,唯一索引,主键索引,全文索引
1.普通索引:允许重复的值出现,可以在任何字段上面添加
2.唯一索引:除了不能有重复的记录外,其它和普通索引一样,可以在值是唯一的字段添加(用户名、手机号码、身份证、email,QQ),可以为null,并且可以有多个null
3.主键索引:是随着设定主键而创建的,也就是把某个列设为主键的时候,数据库就会给该列创建索引。这就是主键索引.唯一且没有null值(也被称为非聚集索引)
4.全文索引:用来对表中的文本域(char,varchar,text)进行索引, 全文索引针对MyISAM有用
CRUD:
1)添加
1.一般来说,普通索引的创建,是先创建表,然后在创建普通索引
create index 索引名 on 表 (列1,列名2,...);
alter table 表名 add index 索引名(列1,列名2,..);
ex:
create table aaa(id int unsigned,name varchar(32));
create index nameIndex on aaa(name);
alter table aaa add index index1(name);
2.唯一索引:
①当表的某列被指定为unique约束时,这列就是一个唯一索引
ex:create table bbb(id int primary key auto_increment , name varchar(32) unique); -- 这时, name列就是一个唯一索引.
②在创建表后,再去创建唯一索引
create unique index 索引名 on 表名 (列1,列2,..);
alter table 表名add unique index 索引名 (列1,列2,..);
ex:create table ccc(id int primary key auto_increment, name varchar(32));
注意:unique字段可以为NULL,并可以有多NULL, 但是如果是具体内容,则不能重复.
主键字段 不能为NULL,也不能重复.
3.主键索引:
①创建表时指定主键
ex:create table ddd(id int unsigned primary key auto_increment ,name varchar(32) not null defaul ‘’); -- 这时id 列就是主键索引.
②如果你创建表时,没有指定主键,也可以在创建表后,再添加主键。
alter table 表名 add primary key (列名);
ex: create table eee(id int , name varchar(32) not null default ‘’);
alter table eee add primary key (id);
4.全文索引:
①创建表时定义:
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body)
)engine=myisam charset utf8;
②创建表完成后定义
create fulltext index 索引名 on 表名(列1,列2);
alter table 表名add fulltext index 索引名 (列1,列2);
ex:
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT
)engine=myisam charset utf8;
Alter table article add fulltext index title_body_fulltext_index(title,body);
用法:
错误用法:
explain select * from articles where body like ‘%mysql%’; -- 不会使用到全文索引
正确用法:
explain select * from articles where match(title,body) against(‘database’); -- 会使用全文索引
2) 查询
show index(es) from 表名
3) 删除
alter table 表名 drop index 索引名;
alter table 表名 drop primary key 删除主键。 -- 主键定义为auto_increment时不能删除
4) 修改 : =先删除后添加
3.2.4 注意事项:
索引的代价:
1.占用磁盘空间。
2.对dml操作有影响,因为要维护索引,变慢。
在哪些列上适合添加索引?
较频繁的作为查询条件字段应该创建索引
select * from emp where empno = 1
唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
select * from emp where sex = '男’
更新非常频繁的字段不适合创建索引
select * from emp where logincount = 1
总结: 满足以下条件的字段,才应该创建索引.
a: 肯定在where条件经常使用 或者经常用来排序 order by后面的字段
b: 该字段的内容不是唯一的几个值(sex)
c: 字段内容不是频繁变化.
不会出现在WHERE子句中字段不该创建索引
3.2.5 小技巧:
根据索引列的多少分为复合索引和普通索引
普通索引(单列索引):该索引只在一个列上面创建
复合索引(多列索引):该索引只在多个列上面创建
1.对于创建的多列索引(复合索引),不是使用的第一部分就不会使用索引。
alter table dept add index my_indx (dname,loc); // dname 左边的列,loc就是 右边的列
explain select * from dept where dname='aaa'\G 会使用到索引
explain select * from dept where loc='aaa'\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.
3.如果条件中有or,有条件没有使用索引,即使其中有条件带索引也不会使用。换言之,就是要求使用的所有字段,都必须单独使用时能使用索引.
explain select * from dept where dname = 'aaa';
explain select * from dept where loc = 'aaa';
select * from dept where dname=’xxx’ or loc=’xx’;
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来。否则不使用索引。
expain select * from dept where dname=’111’;
expain select * from dept where dname=111; (数值自动转字符串)
expain select * from dept where dname=qqq; 报错
也就是,如果列是字符串类型,无论是不是字符串数字就一定要用 ‘’ 把它包括起来.
5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引。
表里面只有一条记录
3.3 分表
- 分为水平分割(行)和垂直分割(列)
3.3.1 水平分表
一张表的数据太多时候要分表 ex:一张表有1000条数据水平拆分成10个100条数据
如果一张表中数据量巨大时,我们要经常查询。则可以按照合适的策略拆分为多张小表。尽量在单张表中查询,减少扫描次数,增加查询效率。如果拆分不好,经常组合(union)查询,还不如不拆分.
分表策略:
1.按时间分表:这种分表方式有一定的局限性,当数据有较强的实效性,如微博发送记录、微信消息记录等,这种数据很少有用户会查询几个月前的数据,如就可以按月分表。
2.按区间范围分表:一般在有严格的自增id需求上,如按照user_id水平分表:
table_1 user_id从1~100w
table_2 user_id从100W+1~200w
table_3 user_id从200W+1~300w
3.hash分表:通过一个原始目标的ID或者名称通过一定的hash算法计算出数据存储表的表名,然后访问相应的表。
最简单hash算法: T_user + Id%100+1 复杂hash算法:...
3.3.2 垂直分表
如果一张表某个字段,信息量大且不经常查询,则可以考虑把这些字段,单独的放入到另一张表中,用外键关联。如果硬是要查询,就是用跨表查询(join)
3.4 分区
什么是分区?
不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。
查看数据库是否支持
SHOW VARIABLES LIKE '%partition%';
分区作用?
从MySQL 5.1 中新增了分区(Partition)功能,优势也越来越明显了:
1.与单个磁盘或文件系统分区相比,可以存储更多的数据
2.很容易就能删除不用或者过时的数据
3.一些查询可以得到极大的优化 可以并发查询
4.涉及到 SUM()/COUNT() 等聚合函数时,可以并发进行
5.IO吞吐量更大(多台服务器)
分区方式?
常见分区方式:
①Range(范围) –基于一个给定的连续空间,把数据分配到不同分区。1-10 11-20
②List(预定义列表) –类似Range分区,区别在List分区是基于枚举出的值列表分区,而 Range分区是根据给定的连续区间范围分区 1,2,3 4,5,6
③Hash(哈希)–这中模式允许通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区。例如可以建立一个对表主键进行分区的表。这个根据给定的分区个数,把数据分配到不同的分区。
④Key(键值)-上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。
Composite(复合模式) –以上模式的组合使用
分区空值处理:
1、range方式,默认放入最小值分区
2、list方式,必须指定null值匹配
3、hash方式,默认当成0
3.5 SQL优化小技巧 变多次维护为一次维护
SQL:ddl dml dql
DDL优化:
1 、通过禁用索引来提供导入数据性能 。 这个操作主要针对有数据库的表,追加数据
//去除键
alter table test3 DISABLE keys;
//批量插入数据
insert into test3 select * from test;
//恢复键
alter table test3 ENABLE keys;
变多次索引维护为一次索引维护
2、 关闭唯一校验
set unique_checks=0 关闭
//批量插入数据
insert into test3 select * from test;
set unique_checks=1 开启
变多次唯一校验为一次唯一校验
3、修改事务提交方式(导入)
set autocommit=0 关闭
//批量插入
set autocommit=1 开启
变多次事务提交为一次事务提交
DML优化:
insert into test values(1,2);
insert into test values(1,3);
insert into test values(1,4);
//合并多条为一条
insert into test values(1,2),(1,3),(1,4)
变多次事务提交为一次事务提交
DQL优化:
放弃索引进行全表扫描要注意:!= <> is null or in not like模糊查询前置匹配
索引创建原则:在where或order by后面的列考虑建立索引 经常被查询且唯一性高的字段 不频繁更改的字段
1)1 order by优化
1.多用索引排序
2.普通结果排序(非索引排序)Filesort
索引本身就是排序的,所以多使用索引。
2)group by优化
查询某个时间的付款总和
explain
select DATE_FORMAT(payment_date,'%Y-%m'),sum(amount) from payment GROUP BY DATE_FORMAT(payment_date,'%Y-%m') ;
explain
select DATE_FORMAT(payment_date,'%Y-%m'),sum(amount) from payment GROUP BY DATE_FORMAT(payment_date,'%Y-%m') order by null;
在group by是使用order by null,取消默认排序
3) subQuery嵌套优化
在客户列表找到不在支付列表的客户
#在客户列表找到不在“支付列表”的客户 , 查询没买过东西的客户
explain
select * from customer where customer_id not in (select DISTINCT customer_id from payment); #子查询 -- 这种是基于func外链
explain
select * from customer c left join payment p on(c.customer_id=p.customer_id) where p.customer_id is null -- 这种是基于“索引”外链
4)or优化
在两个独立索引上使用or的性能优于
1. or两边都是用索引字段做判断,性能好!!
2. or两边,有一边不用,性能差
3. 如果employee表的name和email这两列是一个复合索引,但是如果是 :name='A' OR email='B' 这种方式,不会用到索引!
5)limit优化
select film_id,description from film order by title limit 50,5;
select a.film_id,a.description from filqm a inner join (select film_id from film order by title limit 50,5)b on a.film_id=b.film_id
30中sql语句优化
https://www.cnblogs.com/Little-Li/p/8031295.html
4、多机优化
4.1 集群(读写分离)
原理:
集群:主从同步+读写分离+主备切换
主从同步:在主服务器中配置生成二进制文件 并授权给一个账户 从服务器拿到该账户去访问二进制文件 还原成自己的日志 再还原成数据
读写分离:通过mysql_proxy来代理管理主从数据库服务器 并对外暴露一个虚拟的端口(主从服务器中应该有同一个用户)
主备切换:防止单点故障
具体如何配置可自行百度~
4.2 分布式
按业务分要访问哪一个集群,即将不同模块的业务分到不同数据库集群上面
5、缓存
详情看redis部分