luotuofeile 2020-02-13
目录
网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。例如:双十一购物
双十一购物节,号称优惠比较多。所以很多人都在那天购物,特别是11.11凌晨的时候,成千上万或上亿,一起查询、浏览商品,下单购物。实质上,商品订单等信息是保存到数据库中的,那是不是很多人同时获取数据库连接,同时操作数据(读写),操作完成后关闭连接。那,是不是要同时支持很多连接,并且读写。那,现状我们一台数据库最大连接数是不是有限的。不能满足很多人同时读写,是有瓶颈
解决方案 ==集群和分布式==
网站产生的数据量是巨大的,对于关系型数据库来说,在一张包含海量数据的表中查询,效率是非常低的.例如:商品表
像天猫、京东等购物商城,某些数据是巨大的,比如说商品,用户等。存放到数据库中会有怎么样的效果呢?是不是一张包含很多数据(上亿条记录)的表,那我们查询、插入起来是不是速度会很慢。也就是说海量数据表的的读写速率比较低。
解决方案 ==分表,分库==
在基于web的结构(即浏览器/服务器)当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库却没有办法简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展 是非常痛苦的事情,往往需要停机维护和数据迁移.
动态添加服务器一台数据库服务器扩充到多台时,不下电情况是很难做到的。
单点故障:一台数据库服务器挂了。业务就中断,期望去找还好的数据库继续提供服务。
原理
==所有sql都是mysql执行,当它发现客户端执行sql慢的时候,会把结果记录下来。方便用户查找定定位==
A : 数据库以慢查询的方式启动(这样的话就可以把慢查询记录到日志文件中了)
步骤三:重启MySql服务器
一些常见的sql
#查看数据库运行时间 单位是秒 指MySql服务器启动了多少秒 show status like 'uptime' #CRUD执行次数 一般来说 查询操作占八成 show status like '%COM_%' show status like '%com_select%' show status like '%com_insert%' #查询所有连接数 show status like 'connections' #查看最大连接数 可以作为my.ini中最大连接数配置的依据 show status like 'Max_used_connections' #查询当前系统的慢查询时间 默认10s,一般来说2s算快 show variables like 'long_query_time' ; #查看当前查询中慢查询有几个 show status like 'slow_queries' select count(*) from emp select * from emp where empno = 2600000 #修改数据库的慢查询时间 set global long_query_time=0.5 #以慢查询的方式启动数据库 #1.关闭原来的数据库 2.以慢查询的方式启动数据库 3.设置慢查询时间 4.模拟慢查询 5.查看日志 #什么时候开启慢查询记录日志 #1.开发的时候 自测 #2.测试人员测试的时候
通过慢启动找到了执行比较慢的Sql,接下来就应该分析这些执行慢的Sql的原因了
通过一下sql语句分析(==Sql语句前面加一个explain==)
explain select * from emp where empno = 2600500;
主要分析四个参数
==针对单机优化==
① 表结构优化
答:
首先我会从表的表结构设计上优化,建表时遵守三范式(第一范式:列的原子性,列不可分割,第二范式,表中行记录唯一,设置主键,第三范式,表中不要有冗余字段,即采用外键。),当然如果遇到一些特殊的情况,我也会采取反三范式的手段。给表中一些冗余字段。提高运行运行速率。(但采用反三范式会涉及到数据更新问题。解决方案有①用redis做作异步刷新②同步。同步的方式又有两种。一种是写两条sql,第二种是用触发器(达到某种条件就触发)),
==触发器==
```
#有个文章表 有个文章分类表 当 文章表新增数据的时候 文章分类表应该更新总的数量
create table board1(id int primary key auto_increment,name varchar(50),articleCount int);
create table article1(id int primary key auto_increment,title varchar(50),bid int references board1(id));
insert into board1 value (null,‘test‘,0);
create trigger insertArticle_Trigger after insert on article1 for each row
begin
update board1 set articleCount=articleCount+1 where id= NEW.bid;
```
其次我会做一些字段的长度优化:比如定长,变长。就是用合适的长度。 然后我会从数据库的引擎选择出发。选择合适的数据库引擎。如果数据量较大(500w)使用水平分表。单表字段较多使用垂直分表。
MyIsam和InNoDB的区别 ①MyIsam不支持事务,InnoDB支持事务 ②MyIsam不支持外键,InnoDB支持外键 ③MyIsam是表锁,InnoDB是行锁 ④相对来说MyIsam的效率比InnoDB效率高 ⑤MyIsam支持全文检索,InnoDB不支持全文检索 #指定存储引擎 如果创建表时没有指定 使用默认的 每一张表都可以单独指定存储引擎 create table test (id int,name varchar(50)) create table test1 (id int,name varchar(50)) ENGINE myisam #修改存储引擎 alter table test1 engine = innodb explain select * from emp where empno=2000023 #查看表是否创建索引 show indexes from emp
最后我会从SQL出发,通过数据库的慢查询日志,找到执行较慢的SQL,通过explain命令分析速度慢的原因。通过建立合适的索引,提高查询效率。
## 3. 索引
### 3.1 什么是数据库索引
答:我理解的数据库索引就是帮助数据库高效获取查询数据的一种数据结构。
### 3.2 索引算法
FullText全文索引算法,myisam,只能能在char vachar text
hash就像Map,通过一个key直接就能找到value
#查看表是否创建索引 show indexes from emp # 单列索引 多列索引(复合索引 多个列共同组成) show indexes from dept alter table dept add index my_indx (dname,loc); #对于创建的多列索引(复合索引),不是使用的第一部分就不会使用索引。 explain select * from dept where dname = 'OUhcNKkXfL'#使用了索引 explain select * from dept where loc = 'mbvaQcPS' #不会使用到索引 explain select * from dept where dname = 'OUhcNKkXfL' and loc = 'mbvaQcPS'#会使用到 explain select * from dept where loc = 'mbvaQcPS' and dname = 'OUhcNKkXfL' #会 explain select * from dept where dname = 'OUhcNKkXfL' or loc = 'mbvaQcPS' #不会 or导致索引失效 explain select * from dept where dname like 'OUhcNKkXfL' #会使用到索引 explain select * from dept where dname like '%OUhcNKkXfL%'#不会使用 explain select * from dept where dname like '%OUhcNKkXfL'#不会 explain select * from dept where dname like 'OUhcNKkXfL%'#会 模糊查询前置匹配导致索引失效
答:① 经常作为where后面的条件字典 或者 order by后面的字段
②唯一性较差的字段不适合创建索引 如性别 男女
③ 频繁更新的字段不适合创建索引
==前提==:单表数据量较大时可以采用水平分表的策略,提交查询效率。(大约500w数据左右)
==分表策略==
①从业务逻辑
②地区
③时间(像微博的月份的)
如果一张表中数据量巨大时,我们要经常查询。则可以按照合适的策略拆分为多张小表。尽量在单张表中查询,减少扫描次数,增加查询效率。如果拆分不好,经常组合(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算法计算出数据存储表的表名,然后访问相应的表。
Teacher1
Teacher2
生成id的方式:主键自增 uuid(java) 雪花算法(分布式主键)
uuid:7fad3434234532 .hashcode 得到一个数值
数字%3 = 0 1 2 (得到三张表0 . 1. 2)
最简单hash算法: T_user + Id%100+1 复杂hash算法:
分表后可能涉及跨表查询
select * from dept where deptno between 101 and 105 union all select * from dept where deptno between 106 and 110
==union 和 union all 的区别:union会去掉两张表重复的数据,union all不会去重==
==定义== 有些表记录数并不多,可能也就2、3万条,但是字段却很长,表占用空间很大,检索表时需要执行大量I/O,严重降低了性能。这个时候需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系(外键)。
比如:我的那个分布式项目我做的课程模块因为考虑到课程表字段太多。考虑到一些字段使用的频率不是很高。所以使用了垂直分表的模式。更具业务逻辑分为课程表,课程详情表,课程营销表,课程资源表。通过外键方式关联表数据。提高数据库性能。
like 模糊查询前置匹配
or连接索引失效
不等于操作 !=
is null判断
in或者not in
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5.下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%‘
若要提高效率,可以考虑全文检索。
6.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where
可以改为强制查询使用索引:
select id from t with(index(索引名)) where
8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=‘abc‘--name以abc开头的id
select id from t where datediff(day,createdate,‘2005-11-30‘)=0--‘2005-11-30‘生成的id
应改为:
select id from t where name like ‘abc%‘
select id from t where createdate>=‘2005-11-30‘ and createdate<‘2005-12-1‘
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
13.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
30.尽量避免大事务操作,提高系统并发能力。
==SQL:ddl (define 创建 修改 删除Drop) dml(manage 增加 insert 修改update 删除) dql(query select)==
==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优化==
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‘ 这种方式,不会用到索引!
4)==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
一、游标简介
1、游标简介
游标的设计是一种数据缓冲区的思想,用来存放SQL语句执行的结果。游标是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。
尽管游标能遍历结果中的所有行,但一次只指向一行。
游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作。
2、游标的特性
游标具有三个属性:
A、不敏感(Asensitive):数据库可以选择不复制结果集
B、只读(Read only)
C、不滚动(Nonscrollable):游标只能向一个方向前进,并且不可以跳过任何一行数据。
3、游标的优点
游标是针对行操作的,对从数据库中SELECT查询得到的结果集的每一行可以进行分开的独立的相同或不同的操作,是一种分离的思想。游标是面向集合与面向行的设计思想之间的一种桥梁。
4、游标的缺点
游标的主要缺点是性能不高。
游标的开销与游标中进行的操作相关,如果在游标中进行复杂的操作,开销会非常高。如果采用面向集合的SQL语句,扫描成本为O(N);但如果采用面向集合的SQL语句的扫描成本为O(N*N),则使用游标有可能会带来性能上的提升。
游标的缺点是只能一行一行操作。在数据量大的情况下,速度过慢。数据库大部分是面对集合的,业务会比较复杂,而游标使用会有死锁,影响其他的业务操作,不可取。 当数据量大时,使用游标会造成内存不足现象。
5、游标的适用场景
MySQL数据库中,可以在存储过程、函数、触发器、事件中使用游标。
二、游标的操作
1、游标的定义
DECLARE cursor_name CURSOR FOR select_statement
2、打开游标
OPEN cursor_name;
3、取游标中的数据
FETCH cursor_name INTO var_name [, var_name]...
4、关闭游标
CLOSE cursor_name;
5、释放游标
DEALLOCATE cursor_name;
三、游标实例
1、创建一张游标的测试表
CREATE TABLE cursor_table ( id INT , name VARCHAR(10), age INT )ENGINE=innoDB DEFAULT CHARSET=utf8; insert into cursor_table values(1, '孙悟空', 500); insert into cursor_table values(2, '猪八戒', 200); insert into cursor_table values(3, '沙悟净', 100); insert into cursor_table values(4, '唐僧', 20); 使用三种方式使用游标创建一个存储过程,统计年龄大于30的记录的数量。
2、Loop循环
CREATE PROCEDURE getTotal() BEGIN DECLARE total INT; ##创建接收游标数据的变量 DECLARE sid INT; DECLARE sname VARCHAR(10); #创建总数变量 DECLARE sage INT; #创建结束标志变量 DECLARE done INT DEFAULT false; #创建游标 DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; #指定游标循环结束时的返回值 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; #设置初始值 SET sage = 0; SET total=0; #打开游标 OPEN cur; #开始循环游标里的数据 read_loop:loop #根据游标当前指向的一条数据 FETCH cur INTO sid,sname,sage; #判断游标的循环是否结束 IF done THEN LEAVE read_loop; #跳出游标循环 END IF; #获取一条数据时,将count值进行累加操作,这里可以做任意你想做的操作, SET total = total + 1; #结束游标循环 END LOOP; #关闭游标 CLOSE cur; #输出结果 SELECT total; END
call getTotal();
3、While循环
CREATE PROCEDURE getTotal() BEGIN DECLARE total INT; ##创建接收游标数据的变量 DECLARE sid INT; DECLARE sname VARCHAR(10); #创建总数变量 DECLARE sage INT; #创建结束标志变量 DECLARE done INT DEFAULT false; #创建游标 DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; #指定游标循环结束时的返回值 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; SET total = 0; OPEN cur; FETCH cur INTO sid, sname, sage; WHILE(NOT done) DO SET total = total + 1; FETCH cur INTO sid, sname, sage; END WHILE; CLOSE cur; SELECT total; END
4、Repeat循环
CREATE getTotal() BEGIN DECLARE total INT; ##创建接收游标数据的变量 DECLARE sid INT; DECLARE sname VARCHAR(10); #创建总数变量 DECLARE sage INT; #创建结束标志变量 DECLARE done INT DEFAULT false; #创建游标 DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age > 30; #指定游标循环结束时的返回值 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; SET total = 0; OPEN cur; REPEAT FETCH cur INTO sid, sname, sage; IF NOT done THEN SET total = total + 1; END IF; UNTIL done END REPEAT; CLOSE cur; SELECT total; END