目录
一、MySQL基础知识
1.1 表结构(create table)
1.2 常用的SQL操作
二、问题SQL的定位
2.1 全面日志查询
2.2 慢SQL日志的生成、分析
2.2.1 配置文件在my.cnf
2.2.2 慢SQL分析
2.3 进程列表查询
2.4 SQL运行的步骤
2.5 执行计划的查看
2.5.1 SQL的执行计划
2.5.2 explain 命令分解
三、SQL并发问题
3.1 锁
3.2 事务3.3 并发引起的问题3.4 其它锁问题
四、MYSQL基本分析命令
五、索引
5.1 索引的类型及作用
5.2 索引的存储类型
5.3 索引类型
5.3.1 单列索引
5.3.2 多列索引(组合索引)
5.4 索引的作用
5.5 索引的缺点
5.6 索引的优化(空间换时间)
5.7 性能调优的原则
六、SQL调优
6.1 索引失效
6.2 常见的表连接方式
6.3 发现mysql有问题的手段
6.4 常见优化实例
6.5 SQL调优
6.5.1 数据库配置
6.5.2 表级优化(业务层优化)
6.5.3 大数据的优化(分库分表、读写分离、大数据库)
6.5.4 库表切分(针对千万级别的表,进行优化)
6.5.5 主从库复制原理
6.5.6 数据的读写分离
七、存储过程
7.1 概述
7.2 存储过程缺点
7.3 存储过程建立及调用
7.4 变量的使用及传递
7.4.1 输出存储过程查询到的值
7.4.2 变量的定义
7.5 if 、case、loop、while的使用
7.6 游标
7.7 存储过程批量造数据
八、生产全链路压测
主要是用于定义或改变表的结构,数据类型,表之间的链接和约束等初始化工作上,大多在建立表时使用,常用的命令有create、alter、drop等
SELECT、UPDATE、INSERT、DELETE
设置或更改数据库用户或角色权限的语句常用的是grant
路径:/etc/my.conf
[mysqld]
general_log=0 //1开 0关
general_log_file=c:\usr\local\mysql5\data\
mysqld配置信息:
命令行查询慢日志设置:
use phpwind; show variables like '%query%'; # 查看配置相关的信息
show variables like 'long_query_time%'; # 查看慢SQL设置的时间
命令行修改慢日志设置:
set global slow_query_log =0;
set global long_query_time=1;
修改后,只在当前数据库生效。mysql重启后,会去读my.cnf配置文件的信息
慢日志分析工具:mysqldumpslow
s:是表示按照何种方式排序
c:访问次数
i:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:即为返回前面多少条数据
g:后边搭配一个正则匹配模式,大小写不敏感
mysqldumpslow -s c -t 20 xxx-slow.log # 访问次数最多的20个sql语句
mysqldumpslow -s r -t 20 xxx-slow.log # 返回记录集最多的20个sql
mysqldumpslow -s t -t 10 -g "left join" /data/mysql/mysql-slow.log # 得到按照时间排序的前10条里面含有做了连接的查询SQL
explain select count(*) from pw_bbs_threads; # 查询语句前面加一个 eplain 来解析SQL语句
结果各字段简单描述:
show processlist; # 查询数据库的瞬时操作,查看当前连接运行的情况
结果各字段简单描述:
每一条记录代表一个数据库连接数。
软解析(执行步骤1,2,4),查询缓存(library cache)中有执行计划
硬解析(步骤1,2,3,4),查询缓存(library cache)中没有执行计划,需要耗用CPU资源
where 条件,mysql 是从最右侧进行条件匹配,可以对条件的顺序进行调整优化。
2.5.2.1 ID
SQL执行的顺序的标识,从大到小的执行,同数值从下到上执行
2.5.2.2 select_type
SIMPLE:最简单的查询语句,没有子查询或者多表查询
PRIMARY:为复杂查询而建立的首要表,通常在Derived 和 Union中看到
Union: UNION语句中的一个元素(UNION 作用是将多个select语句的结果整合到一个结果中返回)
UNION RESULT: UNION 的结果集
DEPENDENT SUBQUERY: 子查询的结果集
DERIVED:驱动表,或者中间表,只是一个临时存放数据的表
2.5.2.3 table
可能是表名、表的别名,或者一个为查询产生的临时表。
2.5.2.4 type(重点关注)
特殊场景:
常见场景(以下字段,越往下,执行效率越低):
const:表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!const用于用常数值比较PRIMARY KEY或UNIQUE索引的所有部分
eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY
eq_ref可以用于使用" = "操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。
ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY(换句话说,如果联接不能基于关键字选择单个行的话),则使用ref。
如果使用的键仅仅匹配少量行,该联接类型是不错的。ref可以用于使用" = "或" <=> "操作符的带索引的列
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。当使用" = "、" <> "、" > "、" >= "、" < "、" <= "、" IS NULL "、" <=> "、" BETWEEN "或者" IN "操作符,用常量比较关键字列时,可以使用range
index:索引树被全扫描。这通常比ALL快,因为索引文件通常比数据文件小
ALL:全表扫描,很不好
内连接(数据量大的情况下,避免内连接),尽量用left join、right join 进行优化和替换,提高效率。原则上避免,但实际工作中,主要还是关注数据量。
2.5.2.5 possible_keys(重点关注)
查询中用到的索引,如果一个列值多于3个,则说明引用的索引太多,或者存在一个无效的索引。
2.5.2.6 Key(重点关注)
key列显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL
2.5.2.7 Key_len(重点关注)
计算组合索引,索引键的长度,用于确认索引的有效性,及多列索引中用到的索引的数量非常重要
2.5.2.8 ref
对索引进行解析
2.5.2.9 rows
显示MySQL认为它执行查询时必须检查的行数,采用嵌套算法,如第一列查出需要2行,第二列查出有10行,则数值为20,值越大,表示查询用到的数量越多
2.5.2.10 extra(扩展)
using where:
使用了where查询条件,但是没有使用到索引列,需要从表数据中去获取结果
using join buffer:
使用表链接时,没有使用索引,需要用缓冲区来保存结果(中间的值放在buffer里),一般情况下不需要处理(需要注意数量级,内存是有限的)
内存满掉的结果: buffer ---> freesort(由从内存中取值,变为取物理磁盘上的值)
using filesort:
从磁盘读取,使用文件IO来存储中间过程数据,建议做优化处理(加索引)
using temporary:
使用内部临时表来存储中间过程数据,通常在order by 或者 group by 条件中使用
DBA和架构师层面对数据库方面的优化的思考:
基于业务和表结构的优化,这个过程的优化效果是最大的,SQL优化效果和成本并没有想象中的 那么大。连表查询,最大3 ~ 5张表,超过我们就需要从业务的角度思考,有没有这个必要做这个查询,或者优化表结构或业务架构。
BDB存储引擎采用的是页面锁,也支持表级锁。
myISAM存储引擎采用的是表级锁。从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如web应用
表级锁有两种模式:
读锁:不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求(只有当读锁释放后,才会执行其他进程的写操作)
写锁:会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
innoDB存储引擎支持行级锁,也支持表级锁,但默认情况下采用的是行级锁
行级锁更适合于有大量索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生所冲突的概率最低,并发度也最高。
show status like 'table%'; # 分析系统上的表锁定争夺
如果table_locks_waited的值比较高,则说明存在着较严重的表级锁争用的情况。
explain # 查看SQL的执行计划
show create table t1; # 查看表的建表语句
show table status; # 显示表的相关信息
show status # 整体状态的查看
show varlables # 查看当前的配置
show indexes (索引相关的内容)
在通过explain 命令查看索引是否失效(如语句中使用了“like”)
show index from tables; # 查看索引
结果列说明:
Non_unique:如果索引不能包括重复词,则为0,如果可以则为1
key_name:索引的名称
seq_in_index:索引中的列序列号,从1开始
column_name:列名称
collation:列以什么方式存储在索引中。在mysql中,有值‘A’(升序)或NULL(无分类)
cardinality:索引中唯一值的数目的估计值
sub_part:如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为null
packed:指示关键字如何被压缩,如果没有被压缩,则为NULL
NULL:如果列含有NULL,则为YES,否则为NO
index_type:用过的索引方法(Btree\fulltext\hash\Rtree)
索引是一种特殊的文件(lnnoDB数据表上索引是表考核关键的一个组成部分),他包含对数据表所有的记录的引用指针。数据库索引好比是一本书前面的目录,能加快数据库的查询速度。
索引的作用:
FULLTEXT:全文索引
他的出现是为了解决where name like '%Word%'这类针对文本的模糊查询,效率较低的问题。
BTREE:二叉树(mysql默认的储存类型)
存入一个树形的数据结构,每次查询都是从树的入口root开始,一次遍历node,获取leaf。
HASHhash:哈希(目前比较常用)
一种 key => value 形式的键值,对可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。目前更多的用来校验数据的唯一性,效率快。
使用哈希索引存储类型也存在一些问题:
PRIMRY KEY(主键索引):建立一张表,建了主键,系统或主动加上索引,不需添加。一个表中只会有一个主键索引
UNIQUE(唯一索引):数列里面的值只有一个唯一值。唯一索引在表中可以有多个,值是唯一的,作用是保证数据的完整性
INDEX(普通索引):给普通的列添加索引
使用组合索引,第一个列必须出现查询组语句中,这个索引才会被用到,不然会被失效。
最左前缀原则:组合索引的第一个字段必须出现在查询组句中,这个索引才会被用到,创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
show index from pw_bbs_threads # 查看索引
alter table pw_bbs_threads drop index XXX # 删除索引
alter table pw_bbs_threads add index index_fid(fid) # 增加索引
单列索引与多列索引的区别:
利用索引闲置查询读取的行数,利用索引连接表
select * from pw_bbs_threads LEFT JOIN pw_bbs_forum USING (fid) where fid =8;
给fid建立索引后:
影响inset语句的执行,每次插入新数据,都需要更新索引。
当表数据较大时,生成索引需要较长的时间,此时的数据是不可用的,再生产环境执行时,需要特别的注意。
索引需要空间,组合索引如果较大,会占用较多的物理空间 (从性能角度来看,也算不上缺点)
索引优化原理:让SQL少做事,让SQL少找数据
更多的是业务与资源之间找平衡,或者是资源与资源之间找平衡,又或是程序之间的平衡。
内链接:inner join \,\where条件
左链接:left join
右链接:right join
处理表链接的方式,MySQL只有一种:Block Nestde loop,因为只有一种,索引显得特别重要,通过中间表和其他查询条件,进行一 一的匹配。
A、B表连表查询,如果不走索引,会进行4 * 8 = 32次匹配。拿到结果,放在tmp(中间表),在于C表进行一 一对应的匹配
mysql中,中间表的选择是非常重要,他决定了遍历数据的次数和效率。所以,我们尽量在驱动表(where条件最靠右的表)进行按列查询(建立索引)
mysql中的where执行顺序,先执行最右的条件,从右往左执行。大量数据的表放在最右边(第一张表),优先进行过滤。
性能测试的优化的本质是:空间换时间,提高系统效率。
案例1:数据库表结构设计不合理
一张表只有两个字段,一个id,一个txt,但是txt 中存储的是一段业务日志,近10KB的数据。存储很快,但是对数据的读取的影响将会很大
一个表,有20个字段。性能很差,需要对表进行分表处理(对不常用的字段进行独立出来)
案例2:数据库IO错误
很多数据库放置在可用性很差的磁盘上,还有很多数据库的指定的磁盘不正确,这样只是配置了磁盘空间而没有考虑IO带宽。如果1条数据 10KB ,TPS 1000(预估IO带宽需要达到10M/s以上才可能达到1000TPS)。而然每个TPS,还需要操作3次库,以3M/s的速度消耗磁盘,此时需要优化磁盘文件类型。
案例3:多次不必要的连接数据库操作
有一个订单表的操作,程序先在表中增加一条订单记录,然后等待支付系统的返回,在更新数据状态(2次),一个简单的订单入库需要做3次的数据操作。
业务层面优化:增加订单记录时,将数据放入redis缓存,等更新数据状态时,在进行入库的数据操作(Redis设置入库时间,超过时间就不在进行入库操作。 redis可以做负载均衡,只是对账比较麻烦)。
案例4:某些SQL出现慢查询中,但是单独执行的时候确很快(单独执行很快,为什么出现在慢查询日志中?)
通过以下思路进行问题的排查:
1. 变量变更绑定失效(软解析、硬解析的问题)
2. 系统资源瓶颈。在执行的时候刚好CPU处于繁忙状态? IO有问题? 网络有问题?
3. 数据库状态
4. 缓存、开销上的差异。客户端缓存、连接开销、线程的争用
show 相关命令
explain
index
单SQL优化
事务相关 锁(表锁与行锁 、读锁/写锁、 共享锁/排它锁)
线程的递进关系
max_connections =1000 # MySQL的最大连接数
不能盲目提高设置,需要建立在机器能支撑的情况下。连接数越多,介于mysql会为每个连接提供连接缓冲区,就会开销越多的内存。可以根据当前状态的连接数量('conn%'通配符查看),以定夺该值得大小。
query_cache_size =8M # MySQL的查询缓存大小,对于同样的select语句,将直接从缓冲区中读取结果
query_cache_size 设置是否合理?
如果Qcache_lowmem_prunes 的值非常大,则表明出现缓冲不够的情况
如果Qcache_hits 的值也非常大,则表明缓冲使用非常频繁,此时需要增加缓冲大小
如果Qcache_hits 的值不大,则表明你的查询重复率很低,这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲
select 语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲
库表的水平切分(最常见)
特点:
一张表,拆分为多张表。根据user_id 或者 时间,进行水平拆分,需要改业务。以前1KW的数据放在同一张表,现在将1KW的数据放在多张表中,这样单表的数据就开始下降,数据查询性能提升。
库表的垂直切分
特点:
综合处理: 先垂直切分,再水平切分
从而将整个数据库切分成一个分布式矩阵
切分后的一些问题:拆分,不影响数据的一致性问题。拆分后,对于单表查询肯定是会提升性能。但对于多表查询,如使用group by \ left join 等查询,还是会影响性能,可以继续优化
LVS负载均衡集群架构介绍_民工哥博客的技术博客_51CTO博客LVS负载均衡集群架构介绍,为什么需要使用负载均衡呢?这是一个必较重要的问题实际生产环境中某单台服务器已不能负载日常用访问压力时,就需要使用负载均衡,把用户的请求数据分担到(尽可能平均分配)后端所有功能同等的集群的节点上,同样也是为了解决单台服务器故障问题,从而提高用户的访问体验。1、LVS的介绍LVS(linuxvirtualserver)LINUX虚拟服务器,是一个虚拟的服务器集群系统,可以在linux和unix系统中运https://blog.51cto.com/mingongge/2062874
主库将所有的数据更改都记录到二进制日志,从库通过I/O线程把主库的二进制日志复制到备库的中继日志。从库通过SQL线程读取并应用到中继日志。
复制的延迟问题:
mysql只支持异步复制,由于只有一个SQL进程,主库的并行更新在从库上只能串行执行,这是造成延迟的根本原因。
使用replication 技术 进行主从数据同步、写只能在主库、从库用于分担读压力,读可横向扩展(读:毫秒级 阿里:可以做到微妙级)。减少IO的利用,读可以很方面的进行横向扩展。
存储过程简单来说,就是为以后的使用而保存的一条或多条MySQL语句的聚合。存储过程就是业务逻辑和流程的集合,可以在存储过程中创建表,更新数据,删除数据等。
普通业务逻辑尽量不要使用存储过程,定时性的ETL任务或报表统计函数可以根据团队资源采用存储过程处理(报表性数据库)
写一个存储过程来删除另一个存储过程是不被允许的
1. 不可移植性。每种数据库的内部编码都不太相同,当你的系统需要兼容多种数据库时最好不要用存储过程
2. 学习成本高。DBA擅长写存储过程,但不是每个程序员都能写好。后期坑很多,很大的维护成本
3. 业务逻辑存在多处处理的问题。采用存储过程后也就意味着你的系统有一些业务逻辑不是在应用程序中处理。这种架构会增加一些系统维护和调试成本(储存过程无法单步调试)
4. 存储过程和常用应用程序语言不一样。他支持的函数及语法有可能不能满足需求,有些逻辑就只能通过应用程序处理
5. 如果存储过程中有复杂运算的话,会增加一些数据库服务端的处理成本。对与集中式数据库可能会导致系统可拓展性问题
6. 为了提高性能,数据库会把存储过程代码编译成中间运行代码(类似于Java的class文件),更像静态语言。当存储过程引用对象(表、视图等)结构改变后,存储过程需要重新编译才能生效。一般都是在线变更构造的,所以在变更的瞬间要同时编译存储过程,这可能导致数据库瞬间压力上升引发故障。
create PROCEDURE test1() # 创建存储过程
BEGIN
select * from pw_acloud_apis;
END
call test1(); # 调用
drop procedure if exists test1; # 删除存储过程
show create procedure test1; # 显示某一个存储过程的详细信息
show procedure status; # 显示数据库中所有存储的存储过程基本信息
存储过程只能接收一个值,如果需要达到多个值,可以通过游标来控制。
drop PROCEDURE if exists pl_test_out;
create PROCEDURE pl_test_out(out student_name VARCHAR(50)) # 入参的名字,不能跟列名一样,不然查询无结果
BEGIN
select s.name into student_name from student_info s limit 1; # 只能输出一个值
END;
call pl_test_out(@student_name); # 存储过程只能接收一个值
select @student_name; # 输出存储过程结果,存储过程没有办法返回结果集
drop PROCEDURE if exists pl_test_out; # 入参也可以是两个字段
create PROCEDURE pl_test_out(out student_name VARCHAR(50),out student_f float(50,0))
BEGIN
select s.name into student_name from student_info s limit 1;
select s.Fraction into student_f from student_info s limit 1;
END;
call pl_test_out(@student_name,@student_f);
select @student_name,@student_f;
drop PROCEDURE if exists pl_test_in; # 将limit 行数改成where条件,需要传一个in参数来实现
create PROCEDURE pl_test_in(out student_name VARCHAR(50),out student_f float(50,0),in student_id int)
BEGIN
select s.name into student_name from student_info s where s.id = student_id;
select s.fraction into student_f from student_info s where s.id = student_id;
END;
call pl_test_in(@student_name,@student_f,4);
select @student_name,@student_f;
drop procedure if exists pl_test_check;
create procedure pl_test_check(out checkresult varchar(50),in student_id int)
begin
declare v1 float(50,0); # 对变量进行赋值(另一种方式:set @a ='一个新变量')
set student_id =student_id +1; # 对变量进行运算
select s.fraction into v1 from student_info s where s.id = student_id;
# select student_id; # 查询变量的值,作用于调试
# select v1;
if v1 > 60 then
set checkresult = 'PASS';
else
set checkresult = 'FAIL';
end if;
end;
call pl_test_check(@checkresult,3);
select @checkresult; # into 输出值关键字
# 使用语法
if .... end if;
case when ... when ... end case;
...
游标实际上是一种从包括多条数据记录的结果集中每次提取一条记录的机制。游标充当与指针的作用,尽管游标能遍历结果中的所有行,但他一次只指向一行,游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行对应的操作。
步骤:
1. 声明一个游标:declare 游标名称cursor for table select语句 #这里的table可以是你查询出来的任意集合
2. 打开定义的游标: open 游标名称
3. 获得下一行数据:fetch 游标名称 into testrangeid, versionid;
4. 需要执行 的语句(增删改查):这里视具体情况而定
5. 释放游标:close 游标名称;
####随机生成4条相同参数的数据######
# 造数前,需要准备一些插入的随机数对应使用到的函数
select rand_string(); # 随机生成字符串(函数为自定义)
select CONCAT('zhangsan',rand_string()); # 随机姓名
select CEILING(RAND()*2); # 随机性别
select SYSDATE(); # 当前系统时间
select DATE_SUB(SYSDATE(),INTERVAL CEILING(RAND())*30 YEAR); # 与当前系统时间相差三十年以内
# 存储过程
drop procedure if exists inser_test;
create procedure inser_test(in number int)
begin
DECLARE i int DEFAULT 0;
WHILE i < number DO
INSERT INTO procedure_test ( `test_name`, `test_sex`, `test_start_time`, `test_birthday`, `test_study_no`, `test_study_LV` )
VALUES
( 'zhangsan_aaa', '1', '2021-9-8 16:45:12', '1991-9-8 00:00:00', '10000001', 'C0' );
set i = i + 1;
END WHILE;
end;
call inser_test(4);
select * from procedure_test;
#####随机生成100W不同参数的数据【将插入值,用函数进行参数化】 ######
# 造数前,需要准备一些插入的随机数对应使用到的函数
select rand_string(); # 随机生成字符串(函数为自定义)
select CONCAT('zhangsan',rand_string()); # 随机姓名
select CEILING(RAND()*2); # 随机性别
select SYSDATE(); # 当前系统时间
select DATE_SUB(SYSDATE(),INTERVAL CEILING(RAND())*30 YEAR); # 与当前系统时间相差三十年以内
# 存储过程
drop procedure if exists inser_test;
create procedure inser_test(in number int)
begin
DECLARE i int DEFAULT 0;
DECLARE max int;
select max(p.test_study_no) into max from procedure_test p;
select max; # 查询数据中最大编号,在插入数据时进行+1赋值
WHILE i < number DO
INSERT INTO procedure_test ( `test_name`, `test_sex`, `test_start_time`, `test_birthday`, `test_study_no`, `test_study_LV` )
VALUES
(CONCAT('zhangsan',rand_string()),CEILING(RAND()*2),SYSDATE(),DATE_SUB(SYSDATE(),INTERVAL,CEILING(RAND())*30 YEAR),max+1,CONCAT('pp',CEILING(RAND()*2)-1);
set i = i + 1;
END WHILE;
end;
call inser_test(4);
select * from procedure_test;
业务日志回放。在Nginx层加一层拦截,service所有的请求,产生的业务日志存放在一台服务器中将业务日志在测试环境进行回放,施加压力,到达性能测试的目的。
影子系统。通过type来控制,比如 type =1 走正常的生产环境及数据,type =2 是模拟出来的数据,走虚拟出来的压测服务器(虚拟出来的Nginx及服务器,配置和生产一模一样)
业务平移。