文章目录
MySQL的配置文件
- 二进制日志
log-bin用于记录数据库的写行为,用于主从复制 - 错误日志
log-error默认关闭,用于记录严重的错误和警告信息,每次启动和关闭的详细信息 - 查询日志
log默认关闭,记录查询的SQL语句,开启会降低MySQL的整体性能 - 数据文件
- frm文件:存放表结构
- myd文件:存放表数据
- myi文件:存放表索引
- 配置文件
windows:my.ini
Linux:my.cnf
MyISAM和InnoDB的区别
- MyISAM不支持事务处理、外键等高级数据库功能,而InnoDB支持
- MyISAM强调的是性能,其执行速度比InnoDB更快
- MyISAM只有表锁,InnoDB支持行锁(基于索引的行锁)
- InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要检索出这个变量就行
综上,MyISAM引擎适合读多写少的情况,而大量的写操作则需要用InnoDB来保证性能和数据的准确性。而InnoDB也是MySQL5.5之后的默认引擎
SQL执行顺序
SELECT DISTINCT
FROM tbleName1
[INNER|LEFT|RIGHT] JOIN tableName2
ON
[连接条件]
WHERE
[筛选条件]
GROUP BY
[分组条件]
HAVING
[分组后筛选条件]
ORDER BY [排序字段] [ASC|DESC]
LIMIT
[起始索引数,每页条目数]
[size*(page-1),size]
MySQL执行SQL的顺序从From开始,以下是执行的顺序流程
FROM->WHERE->GROUP BY->HAVING->SELECT->DISTINCT->ORDER BY->LIMIT
- FROM tbleName1[INNER|LEFT|RIGHT] JOIN tableName2将两个表中的数据产生笛卡尔积,生成临时表V1。再将ON中连接条件应用到V1的每一行,再生成一个临时表V2。这时再判断是LEFT JOIN还是RIGHT JOIN,然后把刚才相应的表中被过滤的行添加到V2中生成临时表V3。如果FROM中关联了两个表以上则第三表和V3重复上面的过程
- WHERE对临时表V3进行过滤产生中间表V4。应用where筛选器,对上一步生产的虚拟表引用WHERE筛选器,生成虚拟表V4,ON和WHERE最大区别在于,如果在ON应用逻辑表达式那么在第三步OUTER JOIN中还可以把移除的行再次添加回来,而WHERE的移除是不可恢复的
- GROUP BY 对中间表V4进行分组,产生虚拟表V5。GROUP BY子句将中的唯一的值组合成为一组,得到虚拟表V5。如果使用了GROUP BY,那么后面的所有步骤都只能得到的V5的列或者是聚合函数(COUNT、SUM、MAX等)。原因在于最终的结果集中只为每个组包含一行
- HAVING对分组后的记录进行筛选,和WHERE的作用是一样的产生虚拟表V6
- SELECT对虚拟表V6进行列筛选,选择出要表示的列,产生虚拟表V7
- DISTINCT对中间表V7进行去重,产生中间表V8
- ORDER BY对V8中的数据进行排序,产生中间表V9。排序是很需要成本的,除非你必须要排序,否则最好不要指定排序
- LIMIT对中间表V9进行分页,产生最终结果
所有的查询语句都是从from开始执行的,在执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。
索引优化
SQL慢是指执行时间长或等待时间长,原因是
- 查询语句写的有问题
- 索引失效
- 关联查询太多
- 服务器参数配置不合理或者已达到性能瓶颈
什么是索引
排好序的、用于快速查找的数据结构。在数据之外,数据库还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。对于我们Java程序员来说,这种索引就是B TREE索引(多路搜索树)
索引的优点
- 提高查询的速度,提高数据库的效率,降低数据库的IO成本
- 通过索引对数据库进行排序,降低了排序和分组的成本,减少了CPU的消耗
索引的缺点
- 索引的本质也是一张表,保存了主键和索引字段,并指向实体表的字段。索引是用空间来换取时间,缩短查询的时间成本的同时,要牺牲磁盘空间成本来存储索引
- 索引会提高查询速度,但是会降低表的更新速度。索引创建之后,在对数据库表中的数据进行增、删、改等操作之后,相应的索引也需要进行维护
索引的种类
- 单值索引
一个索引只包含一个列,一个表可以有多个单值索引 - 复合索引
一个索引包含多个列 - 唯一索引
索引列的值唯一,但是允许有空值
#创建索引
#对于创建索引时如果是blob和text类型,必须指定length
CREATE [UNIQUE] INDEX indexName ON tableName(columnName1,columnName2)
ALTER TABLE tableName ADD [UNIQUE] INDEX [indexName] ON (columnName)
#删除索引
DROP INDEX indexName ON tableName
#查看索引
SHOW INDEX FROM tableName
左连接建右表
多表建立连接的时候,左连接将索引建在右表的主外键上,右连接将索引建立在左表的主外键上。因为左连接的时候左表的数据全部都有,而右表的数据需要筛选,也就是说主表是一定需要全表扫描的,而子表不需要,所以索引建在子表上
-
哪些情况要创建索引
- 主键自动创建唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表建立联系的字段,外键关系建立索引
- 高并发的情况下,偏向复合索引
- 查询中排序的索引,排序字段若通过索引去访问将大大的提高排序速度
- 查询中统计或分组的字段要创建索引
-
哪些情况不要创建索引
- 表中的记录太少
- 经常增删改的表
- 数据重复且平均分布的表字段
- WHERE条件里面用不到的字段不创建索引
- 频繁更新的字段不适合创建索引,因为每次更新还需要维护索引,加重数据库的IO负担
约定一般来说一张表不要建立超过5个索引
执行计划
使用EXPLAIN来模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析查询语句的性能瓶颈
#使用方法 EXPLAIN + 目标SQL
EXPLAIN SELECT * FROM human
-
id
SELECT的查询序号,表示查询中执行的SELECT子句或者操作表的顺序。id如果相同的话,自上而下执行;id不同的话,id越大越优先执行 -
select type
- simple
简单查询,不包含子查询或者关联查询 - primary
查询中若包含子查询,主查询被标记为primary - subquery
SELECT或WHERE中包含了子查询,子查询会被标记为subquery - derived
在from列表中包含的子查询被标记为derived(衍生),即查的表是其他查询生成的中间表,后面会有一个数字,那是id,可以看出是那一个表的衍生表 - union
如果第二个select出现在union后面,则被标记为union,如果union包含在from子句的子查询中,外层的select被标记为derived - union select
从union的结果中获取结果的select
- simple
-
table
显示这一行数据是关于那一个表的 -
type
重要参数,主要根据这个来判断一条SQL的好坏- system
表中只有一行数据,基本上是系统变量 - const
表只有一行匹配,并在查询开始时就被读取,因此被优化器视为常数。当主键或者唯一索引和一个常数比较时会出现 - eq_ref
每次匹配时只会读取一行,当主键或者唯一非空索引(unique not null index)的全部或部分被使用时会出现 - ref
每次匹配时会读取所有匹配行,当用到了非唯一索引或者唯一索引的一部分时会出现,等值匹配 - range
根据索引选取一定范围内的行,范围匹配 - index
从索引中获取所有的数据,不需要访问原表。(对于innodb引擎,索引中会有主键数据)或者根据索引的顺序进行全表扫描 - all
全表扫描
[system>>eq_ref>ref>range>index>all]起码达到range级别,最好能达到ref
- system
-
possible_keys
可能用到的索引,一个或者多个,查询涉及到的字段只要存在索引就会被列出,但不一定会被使用 -
key
实际使用的索引,不一定在possible_keys中存在,如果是null则没有使用索引 -
key_len
表示使用的索引的长度,可以用来判断多列索引中的哪些部分被使用到了。在不损失精确性的情况下,越短越好 -
ref
匹配时使用到的列或者常数,如果值是func,表示匹配时用到了由函数生成的值(±、*/也算)。如果要看使用到了哪些函数,可以在explain语句执行后使用show warnings -
rows
检查的行数,Innodb引擎是个估计值 -
Extra
包含不适合在其它列中显示但十分重要的信息- Using filesort
说明MySQL会对数据使用一个外部的索引排序,而不是按照表内索引的顺序进行读取。也就是说MySQL的排序没有用到索引,这种情况尽量进行优化 - Using temporary
使用了临时表来保存结果,常见于分组和排序,这种情况非常影响性能,是一定需要进行优化的 - Using index
表示使用了覆盖索引,避免访问了表的数据行。如果同时出现Using where,表明索引被用来执行索引键值的查找,如果没有则表明索引用来读取数据而非执行查找动作 - Using where
表明使用了where过滤 - Using join buffer
表名使用了连接缓存,如果多表关联join特别多的话需要把配置文件中的join buffer调大一点 - impossible where
where子句的值总是false,获取不到任何行 - select tables optimiezd away
在没有order by的情况下,基于索引优化min/max操作,或者对于MyISAM存储引擎优化count(*),不必等到执行阶段再计算,查询执行计划生成阶段即完成优化 - distinct
优化distinct操作,在找到第一匹配的元组之后立即停止找同样的值的操作
- Using filesort
索引优化
- 全值匹配
即建立索引的字段,在查询语句中全部使用到,并且是等值,这是最理想的情况 - 最佳左前缀法则
有的时候我们用不到索引的所有列,但是我们必须要遵循最佳左前缀法则:查询从最左前列开始,并且不跳过中间列,但是如果是等值匹配的情况,abcd和abdc甚至dcba是一样的,自动查询优化器会自动调整 - 不再索引字段上做任何操作
函数,计算,自动或者手动的类型转换都会导致索引失效而转向全表扫描 - 范围条件右边的列无效
索引可以形象理解为,一级一级递增缩小的范围,比如说A区B栋3楼302房,这是一个索引,如果我只说A区B栋,那么找到B栋的时候,后面你不知道是具体几楼,所以说范围检索后面的列索引无效。所以我们会把范围检索放在最后面 - 尽量使用覆盖索引
只访问索引的查询(索引列和查询列一致),减少select* - 使用!=或者<>会导致索引失效变成全表扫描
- 使用is null ,is not null会导致索引失效
- 使用like关键字的时候前面通配符是%的时候(like(’%a’))MySQL索引失效
所以like后面的百分号尽量不要放在左边。非要放左边的话可以使用覆盖索引来解决这个问题,但查询字段不可以超过建立索引的字段 - 字符串不加单引号会导致索引失效
- 使用or连接时会导致索引失效
查询优化
- 慢查询日志的开启并捕获慢SQL
- explain慢SQL分析
- show profile查询SQL在MySQL服务器里面的执行细节和生命周期情况
- SQL数据服务器的参数调优
小表驱动大表
我们在Java中写两个嵌套的循环,循环次数是两个循环的乘积,所以内层循环和外层循环的顺序没有关系。但是在MySQL中,每执行一次循环就需要建立一次连接,所以要求的是外层循环次数要比内层循环次数要小,也就是我们常说的小表驱动大表,大表指的是数据多的表,查询相当于是遍历这个表,数据越多遍历次数越多
SELECT * FROM A WHERE ID IN(SELECT ID FROM B);
这种情况会先去查询B表,再去查询A表,也就是说相当于先遍历B再遍历A,那么当B表的数据绝对小于A表的时候使用这种方式
SELECT * FROM A EXISTS( SELECT existsFlag FROM B WHERE A.ID=B.ID)
这种情况会先去查询A表,再去查询B,相当于先遍历A再去遍历B,当A表的数据绝对小于BB表的时候使用这种方式
exists关键字是将主查询的数据放在子查询中进行验证,根据验证结果来决定主查询的数据是否保留。exists只会返回一个boolean值所以select后面写什么没关系
SQL排序
MySQL支持两种排序方式Filesort和Index,Index的效率高,它是指MySQL扫描索引本身完成排序,Filesort的效率低ORDER BY尽量使用Index方式进行排序,避免使用FileSort的方式,如果不在索引列上,FileSort有两种算法,双路排序和单路排序
- 双路排序
两次扫描,得到最终数据。从磁盘中读取满足查询条件的数据的行指针(记录目标行在表中的位置)和需要排序的列,对它们在sort_buffer中进行排序之后将排序结果丢给随机读取缓冲区(read_rnd_buffer),随机读取缓冲区根据指针信息再到磁盘中读取该行的其他字段数据 - 单路排序
一次扫描,得到最终数据。从磁盘中读取满足查询条件行的所有的列,按照Order By在buffer中对数据进行排序,然后扫描排序后的列表进行输出。单路排序的效率更高,避免了第二次读取数据,减少了IO次数,降低了磁盘的开销,但是会使用更多的空间,因为把所有的字段都保存在内存中了
但是因为单路排序需要更大的磁盘空间,如果sort_buffer_size的大小不足,可能会导致两次甚至更多次的IO次数
使用单路排序的条件
- 查询语句所取出的字段类型大小总和要小于max_length_for_sort_data
- 排序字段中不包含text和blob类型
当我们只能使用FileSort的时候我们可以通过增大sort_buffer和增大max_length_for_sort_data这两个属性来提高SQL效率max_length_for_sort_data是指查询列中除去text和blob类型,其他字段大小的和,所以我们当使用了排序的时候,尽量不要使用SELECT *,只查询需要用到的字段
如何保证尽量使用Index排序
- 排序字段和建立索引的字段一致
- 排序字段遵循最佳左前缀原则
使用WHERE语句和ORDER BY语句满足索引最左前列:如果WHERE中使用了索引字段作为等值条件,那么WHERE和ORDER BY连起来看满足最佳左前缀原则也是可以的 - 排序字段要么都升序要么都降序,如果一升一降会产生FileSort
- 排序中有不是索引的列会使用FileSort
GROUP BY的本质是先排序,后分组,所以遵循和ORDER BY一样的原则,只需要注意一点,当可以在WHERE中添加的限定条件,不要写在HAVING中
慢查询日志
MySQL的慢查询日志是MySQL提供的一种日志记录,默认关闭,用来记录MySQL中响应时间超过阈值的语句,具体时间超过long_query_time则被记录到慢查询日志中,long_query_time的默认值是10,意思是运行10秒以上的语句
#查询慢查询日志是否开启
SHOW VARIABLES LIKE '%slow_query_log%'
#开启慢查询日志,只对当前数据库生效,重启后失效,想要永久开启需要修改配置文件(my.cnf)
SET GLOBAL slow_query_log=1
#为慢日志文件指定位置和文件名
SET GLOBAL slow_query_log_file='C:\ProgramData\MySQL\MySQL Server 5.5\Data\slow20190901.log'
Variable_name | Value |
---|---|
slow_query_log | OFF |
slow_query_log_file | C:\ProgramData\MySQL\MySQL Server 5.5\Data\ZJZL-0190630XJ-slow.log |
系统会给一个默认的host_name_slow.log,可以通过slow_query_log_file来设置
#查看当前阈值,多少秒会被记录到慢日志中。
#判断逻辑是大于,刚好等于阈值不会记录到慢日志中
SHOW VARIABLES LIKE '%long_query_time%'
#修改慢SQL阈值
SET GLOBAL long_query_time=3
Variable_name | Value |
---|---|
long_query_time | 10.000000 |
#查看当前有多少条慢SQL
SHOW GLOBAL STATUS LIKE '%slow_queries%'
mysqldumpslow日志分析工具
mysqldumpslow–help查看帮助信息
- -s
是按照何种方式排序,后面跟ctrl
c:访问次数
t:查询时间
r:返回记录
l:锁定时间
al:平均锁定时间
ar:平均返回记录数
at:平均返回查询时间 - -t
后面跟一个数字,表示返回前多少条符合条件的数据 - -g
后面接一个正则表达式
得到返回记录数最多的前十条SQL
mysqldumpslow -s r -t 10/慢日志路径
得到按时间排序最长的且有左连接查询的前十条数据
mysqldumpslow -s t -t 10 -g"left join"/慢日志路径
Show Profile
是MySQL提供的可以用来分析当前会话中语句执行的资源消耗情况,默认关闭,并保存最近15次的运行结果
#查看是否开启show profile
SHOW VARIABLES LIKE'%profiling%'
#开启show profile
SET GLOBAL profiling=1
#使用show profiles
SHOW profiles
我们可以看到每一条被记录的SQL的执行时间
QUERY_ID | Duration | Query |
---|---|---|
1 | 0.00190025 | show variables like’%profiling%’ |
通过下面语句,在字段QUERY后面写上对应的QUERY ID可以得到具体的每一个执行流程的时间
show profile主要参数
- ALL
显示所有开销信息 - BLOCK IO
显示块IO相关开销 - CONTEXT SWITCHES
上下文切换相关开销 - CPU
显示CPU相关开销信息 - IPC
显示发送和接收相关开销信息 - MEMORY
显示内存相关开销信息 - PAGE FAULTS
显示页码错误相关开销信息 - SOURCE
显示Source_function,Source_file,Source_line相关开销信息 - SWAPS
显示交换次数相关开销的信息
SHOW PROFILE CPU,BLOCK IO FOR QUERY 1
主要关注下面这些参数的时间,出现任意一个都是必须要进行优化的
- Converting HEAP to MyISAM
查询结果太大,内存不足,需要把查询结果搬运到磁盘中 - Creating temp table
创建临时表:把数据拷贝到临时表中,用完再删除临时表 - Copy to temp table on disk
把内存中的数据拷贝到磁盘中,非常危险的情况 - Locked
加锁
全局查询日志
可以记录所有的SQL语句,并存储在系统表中,永远不要再生产环境开启这个功能
#开启全局查询日志
SET GLOBAL general_log =1
#以表的形式
SET GLOBAL log_output='TABLE'
#查看全局查询日志
SELECT * FROM mysql.general_log
MySQL锁机制
锁的分类
对数据的操作类型划分
- 读锁
针对同一份数据,多个读操作可以同时进行而不会互相影响 - 写锁
当前写操作没有完成前,会阻断其他的写锁和读锁
对数据操作的粒度划分
- 行锁
- 表锁
表锁
偏向MyISAM存储引擎,开销小,加锁快,不会出现死锁,但是锁的粒度大,发生锁冲突的概率高,并发度很低
#手动添加表锁(读锁|写锁)
LOCK TABLE [tableName [READ|WRITE]] , [ ] , [ ]……
#查看表上加过的锁
SHOW OPEN TABLES
#释放表锁
UNLOCK TABLES
表读锁
加锁session可以读加锁表,不可以写(报错),不可以读写其他表(报错),只读不可写
其他session可以读加锁表,不可以写(阻塞),可以读写其他未加锁表,可读写阻塞
表写锁
加锁session 可以读加锁表,可以写加锁表,不可以读写其他表,可读可写
其他session 不可以读(阻塞),不可以写,可以读写其他表,读阻塞不可写
- 加锁session只可以处理自己加锁的表
- 读锁阻塞其他session的写操作,加锁session不可以写
- 写锁阻塞其他session的所有操作,加锁session可读可写
行锁
偏向InnoDB存储引擎,开销大,加锁慢,可能会出现死锁,但是锁的粒度最小,发生锁冲突的概率最小,并发度最高
InnoDB存储引擎默认的事务隔离级别是不可重复读,这种隔离级别下的行锁有以下特点
- 加锁session可以写,可以读最新的、未提交的数据,读己之所写
- 其他session读不到未提交的数据,并且阻塞对同一行的写操作
索引失效会导致行锁升级为表锁
间隙锁
当我们用范围条件而不是等值条件来给数据加锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但是并不存在的数据被称为间隙。InnoDB也会对这个间隙加锁,这种锁机制就叫间隙锁。
间隙锁会导致范围内,不存在的键值也被锁定,所以在锁定后无法插入键值范围内的任何数据,某些情况会影响性能
如何锁定一行
使用关键字FOR UPDATE,直到提交才会释放锁
SELECT * FROM tableName WHERE id=1 FOR UPDATE
主从复制
slave会从master读取binlog来进行数据同步,master将改变记录到二进制文件中,这些记录过程叫做二进制日志事件,slave将maser的二进制文件拷贝到它的中继日志中,slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL的主从复制是,异步且串行化,所以会有延迟的问题。注意:
- 每个slave只能有一个master
- 一个master可以有多个slave
- 每个slave只能有一个唯一的服务器ID
主机修改配置文件my.ini
- server-id 必须
这是服务器的唯一ID - log-bin 必须
启用二进制日志
本地路径/mysqlbin - log-err 可选
启用错误日志
本地路径、mysqlerr - binlog-ignore-db 可选
设置不需要复制的数据库
后面写数据库的名字 - binlog-do-db 可选
设置需要复制的数据库
从机只需要配置服务器ID即可
在主机上建立账户并授权给从机
- CRANT REPLICATION SLAVE ON *.* ‘userName’ @ ‘主机IP’ IDENTIFIED BY ‘passWord’
把userName,passWord替换成想要建立的账户,使用MySQL的命令行来执行 - flush privileges
刷新 - show master status
记录File和Postion的值,记录要复制哪一个二进制文件和要开始复制的位置,注意,这时候不要操作主机,否则这两个值会变化 - 在从机上配置完成配置
CHANGE MASTER TO MASTER_HOST=‘主机IP’,MASTER_USER=‘userName’,MASTER_PASSWORD=‘passWord’,MASTER_LOG_FILE=’’,MASTER_LOG_POS= - STARE SLAVE
启动主从复制服务
配置完成后使用SHOW SLAVE STATUS命令,如果SLAVE_IO_RUNNING和
SLAVE_SQL_RUNNING同为YES则表示配置成功