1mysql的架构介绍
Mysql简介
概述
高级MySQL
mysql内核
sql优化工程师
mysql服务器的优化
查询语句优化
主重复制
软硬件升级
容灾备份
sql编程
MysqlLinux版本的安装--mysql5.5 查看MySQL的安装位置:which mysqld 参考网址:https://www.cnblogs.com/freeliver54/p/9171748.html
mysql5.5
下载地址 下载地址:https://dev.mysql.com/downloads/mysql/
检查当前系统是否安装过mysql 可以用 rpm -qa|grep mysql看当前的系统进程,如果有mysql服务的就会显示的。例如显示:MySQL-client-5.5.48-1.Linux2.6 MySQL-server-5.5.48-1.Linux2.6
安装mysql服务端(注意提示)(第三方软件包放在opt目录下面)
在opt目录下(安装包MySQL-server-5.5.48-1.Linux2.6.i386.rpm包所在的目录),执行rpm(rpm安装包的安装命令)命令rpm -ivh MySQL-server-5.5.48-1.Linux2.6.i386.rpm安装rpm包。
安装后,会有提示,他会自动给你创建一个mysql的用户(root用户,他暂时没有密码)。这里需要注意,安装后会在mysql这个服务器中创建一个root用户,也会在Linux系统中创建mysql用户和mysql组。
安装mysql客户端
在opt目录下(安装包MySQL-client-5.5.48-1.Linux2.6.i386.rpm包所在的目录),执行rpm(rpm安装包的安装命令)命令rpm -ivh MySQL-client-5.5.48-1.Linux2.6.i386.rpm安装rpm包。
查看Mysql安装时创建的mysql用户和mysql组
可以通过命令cat /etc/passwd|grep mysql查看安装后创建的mysql用户相关信息
可以通过命令cat /etc/group|grep mysql查看安装后创建的mysql组相关信息
可以执行mysqladmin --varsion命令,类似java -version如果有相关信息打出,说明mysql安装成功了
mysql服务的启+停
查看服务端是否开启:ps -ef|grep mysql 并可以查看到该服务的相关信息
开启服务端:service mysql start
关闭服务端:service mysql stop
mysql服务启动后开始连接
首次连接成功 在启动mysql服务端后,直接输入mysql,连接成功(因为首次连接是没设置密码的)
注意这里,因为mysql默认没有密码,所以这里我们没有输入密码就直接连上了
按照安装Service中的提示修改登录密码 (直接按照安装时的提示输入设置密码命令:/usr/bin/mysqladmin -u root password 123456 重新设置密码),重新设置密码后,如果直接输入命令mysql,他会报错。
设置完密码后,重新登录服务端的命令:mysql -u root -p -回车-随后输入密码。就登录进服务端了
自启动mysql服务 在Linux下输入命令 chkconfig mysql on。 也可以使用ntsysv,然后在mysql选项前加入*,代表开机自启动该服务。 可以通过chkconfig -list|grep mysql查看mysql服务在哪些开机级别下处于自启动状态。
修改配置文件位置 *****注意:在启动MySQL服务端的时候,他会去加载配置,可以通过/usr/local/mysql/bin/mysqld --verbose --help |grep -A 1 'Default options'命令来查看mysql使用的配置文件默认路径,他会显示几个文件,按优先级去加载其中的一个配置文件。
5.6版本:cp /usr/share/mysql/my-default.cnf /etc/my.cnf
修改字符集和数据存储路径
查看字符集:show variables like '%char%'或者show variables like 'character%'
修改字符集:修改配置文件/etc/my.cnf (由于默认的是客户端和服务端都用了latin1所以会乱码)
修改内容为:
[client]:default-character-set=utf8
[mysqld]:character_set_server=utf8
character_set_client=utf8
collation-server=utf8_general_ci
[mysql] : default-character-set=utf8
最后重启服务端,不过需要注意的是,原来创建的库它的字符集并没有修改回来。也就是说在修改完配置文件后,只对后续创建的库的字符集生效,对前面创建的库的字符集不生效。
Mysql的安装位置 (Linux下var下面是各种变量的配置文件,etc是所有系统配置启动的配置文件的统一的集散地)
在linux下查看安装目录 ps -ef|grep mysql
/var/lib/mysql/ MySQL数据库文件的存放路径 /var/lib/mysql/test.cloud.pid
/usr/share/mysql 配置文件目录 mysql.server命令及配置文件
/usr/bin 相关命令目录 mysqladmin mysqldump等命令
/etc/init.d/mysql 启停相关脚本
Mysql配置文件
主要配置文件
二进制日志log-bin 在配置文件中,mysqld选项下有个log-bin=指定文件地址
主重复制
错误日志log-error 在配置文件中,mysqld选项下有个log-error=指定文件地址
默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等.
查询日志log
默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源的
数据文件 可以通过配置文件指定数据文件存放在哪里(datadir和socket指定数据文件存放在哪里)
两系统
windows
D:\ProgramFiles\MySQL\MySQLServer5.5\data目录下可以挑选很多库
linux
看看当前系统中的全部库后再进去
默认路径:/var/lib/mysql 在该路径下,执行ls -1F|grep ^d搜索所有目录,可以看到对应每个数据库的目录
frm文件
存放表结构
myd文件
存放表数据
myi文件
存放表索引
如何配置
windows
my.ini文件
Linux
/etc/my.cnf文件
Mysql逻辑架构介绍
总体概览 (和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现存储引擎的架构上,插件式的存储引擎架构将查询处理和其他的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。)
1.Connectors
指的是不同语言中与SQL的交互
2 Management Serveices & Utilities:
系统管理和控制工具
3 Connection Pool: 连接池
管理缓冲用户连接,线程处理等需要缓存的需求。
负责监听对 MySQL Server 的各种请求,接收连接请求,转发所有连接请求到线程管理模块。每一个连接上 MySQL Server 的客户端请求都会被分配(或创建)一个连接线程为其单独服务。而连接线程的主要工作就是负责 MySQL Server 与客户端的通信,
接受客户端的命令请求,传递 Server 端的结果信息等。线程管理模块则负责管理维护这些连接线程。包括线程的创建,线程的 cache 等。
4 SQL Interface: SQL接口。
接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
5 Parser: 解析器。
SQL命令传递到解析器的时候会被解析器验证和解析。解析器是由Lex和YACC实现的,是一个很长的脚本。
在 MySQL中我们习惯将所有 Client 端发送给 Server 端的命令都称为 query ,在 MySQL Server 里面,连接线程接收到客户端的一个 Query 后,会直接将该 query 传递给专门负责将各种 Query 进行分类然后转发给各个对应的处理模块。
主要功能:
a. 将SQL语句进行语义和语法的分析,分解成数据结构,然后按照不同的操作类型进行分类,然后做出针对性的转发到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。
b. 如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的
6 Optimizer: 查询优化器。
SQL语句在查询之前会使用查询优化器对查询进行优化。就是优化客户端请求的 query(sql语句) ,根据客户端请求的 query 语句,和数据库中的一些统计信息,在一系列算法的基础上进行分析,得出一个最优的策略,告诉后面的程序如何取得这个 query 语句的结果
他使用的是“选取-投影-联接”策略进行查询。
用一个例子就可以理解: select uid,name from user where gender = 1;
这个select 查询先根据where 语句进行选取,而不是先将表全部查询出来以后再进行gender过滤
这个select查询先根据uid和name进行属性投影,而不是将属性全部取出以后再进行过滤
将这两个查询条件联接起来生成最终查询结果
7 Cache和Buffer: 查询缓存。
他的主要功能是将客户端提交 给MySQL 的 Select 类 query 请求的返回结果集 cache 到内存中,与该 query 的一个 hash 值 做一个对应。该 Query 所取数据的基表发生任何数据的变化之后, MySQL 会自动使该 query 的Cache 失效。在读写比例非常高的应用系统中, Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。
如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
8 、存储引擎接口
存储引擎接口模块可以说是 MySQL 数据库中最有特色的一点了。目前各种数据库产品中,基本上只有 MySQL 可以实现其底层数据存储引擎的插件式管理。这个模块实际上只是 一个抽象类,但正是因为它成功地将各种数据处理高度抽象化,才成就了今天 MySQL 可插拔存储引擎的特色。
从图2还可以看出,MySQL区别于其他数据库的最重要的特点就是其插件式的表存储引擎。MySQL插件式的存储引擎架构提供了一系列标准的管理和服务支持,这些标准与存储引擎本身无关,可能是每个数据库系统本身都必需的,如SQL分析器和优化器等,而存储引擎是底层物理结构的实现,每个存储引擎开发者都可以按照自己的意愿来进行开发。
注意:存储引擎是基于表的,而不是数据库。
概述:
1.连接层:
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理,授权认证,及相关的安全方案。
在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于ssl的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2.服务层:
第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程,函数等。
在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。
如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
3.引擎层:
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介绍MyISAM和InnoDB
4.存储层:
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
查询说明
Mysql存储引擎
查看命令 在客户端下输入:show engines;或者show variables like '%storage_engine%'
MyISAM和InnoDB 参考对比图(MyISAM和InnoDB存储引擎的对比.png)
阿里巴巴,淘宝用哪个
-------------------------------
额外知识补充:查看某个服务(redis,MySQL等)的安装位置(参考:https://www.cnblogs.com/yangzailu/p/11044904.html)
如果命令 which 和whereis 都找不到安装目录,可使用以下办法:
ps -ef|grep redis
查看某个服务的进程信息,得到进程号
ls -l /proc/进程号/cwd 通过该命令可以得到该服务的安装位置
-------------------------------
2索引优化分析
性能下降SQL慢
执行时间长
等待时间长
查询语句写的烂
索引失效
单值 create index idx_user_name on user(name) 用于select * from user where name=''
复合 create index idx_user_nameEmail on user(name,email) 用于select * from user where name='' and email=''
关联查询太多join(设计缺陷或不得 已的需求)
服务器调优及各个参数设置(缓冲\线程数等)
常见通用的join查询
SQL执行顺序
手写 参考:SQL执行顺序手写.png
机读 参考:SQL机读顺序.png
总结 参考:SQL执行顺序总结.png
Join图 参考:Join图1.png和Join图2.png
建表SQL
7种Join
索引简介(索引有两大功能:查找快,排好序)
是什么
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构
索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往上找到y字母,在找到剩下的sql。如果没有索引,那么你可能需要a----z。
你可以简单理解为"排好序的快速查找数据结构"。
详解(重要)
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
结论
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以文件形式存储在硬盘上
我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。
优势
类似大学图书馆建书目索引,提高数据检索效率,降低数据库的IO成本
通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗
劣势
实际上索引也是一张表,该表保存了主键和索引字段,并指向实体表的记录,所以索引列也是要占用空间的
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如果对表INSERT,UPDATE和DELETE。因为更新表时,MySQL不仅要不存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立优秀的索引,或优化查询语句
mysql索引分类 (优先考虑复合索引)(建议一张表索引不要超过5个)(使用时,只能用一个索引)
单值索引
即一个索引只包含单个列,一个表可以有多个单列索引
唯一索引(主键是一种唯一索引)
索引列的值必须唯一,但允许有空值
复合索引(可能只使用了其中的一部分)
即一个索引包含多个列
基本语法
创建
CREATE [UNIQUE如果是唯一索引就要加上这个] INDEX indexName ON mytable(columnname(length));
如果是CHAR,VARCHAR类型,length可以小于字段实际长度;
如果是BLOB和TEXT类型,必须指定length。
ALTER mytable ADD [UNIQUE] INDEX [indexName] ON(columnname(length));
删除
DROP INDEX [indexName] ON mytable;
查看
SHOW INDEX FROM table_name\G
使用ALTER命令
ALTER TABLE tbl_name ADD PRIMARY KEY(column_list);该语句添加一个主键,这意味着索引值必须是唯一的,且不能为null。
ALTER TABLE tbl_name ADD UNIQUE index_name(column_list);这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD INDEX index_name(column_list);添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name(column_list);该语句指定了索引为FULLTEXT,用于全文索引。
**************
普通索引是可以重复的,唯一索引和主键不能重复
唯一索引可以作为数据的一个合法验证手段,例如学生表的身份证号码字段,我们人为规定该字段不得重复,那么就使用唯一索引。(一般设置学号字段为主键)
mysql索引结构
BTree索引 (参考笔记)
Hash索引 了解
full-text全文索引 了解
R-Tree索引 了解
哪些情况需要创建索引
1.主键自动建立唯一索引
2.频繁作为查询的条件的字段应该创建索引
3.查询中与其他表关联的字段,外键关系建立索引
4.频繁更新的字段不适合创建索引
因为每次更新不单单是更新了记录还会更新索引,加重IO负担
5.Where条件里用不到的字段不创建索引
6.单间/组合索引的选择问题,who?(在高并发下倾向创建组合索引) 也就是说推荐使用复合索引
7.查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度
8.查询中统计或者分组字段
哪些情况不要创建索引
1.表记录太少
2.经常增删改的表(经常修改的表)
Why:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT,UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
3.数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。(数据的重复率太高不建议对该字段建索引)
假如一个表有十万条记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。
索引的选择性是指索引列中不同值得数目与表中记录的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。
性能分析
MySQL Query Optimizer
1。MySQL中有专门负责优化select语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为最优的,这部分最耗时间)
2。当客户端向MySQL请求一条query,命令解析器模块完成请求分类,区别出是select并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。
并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件,结构调整等。然后分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或Hint信息还不足以完全确定执行计划,
则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后在得出最后的执行计划。
MySQL常见瓶颈
CPU:CPU在饱和的时候一般发生在数据装入在内存或从磁盘上读取数据时候
IO:磁盘I/O瓶颈发生在装入数据远大于内存容量时
服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
Explain
是什么(查看执行计划)
使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是结构的性能瓶颈
官网介绍
能干嘛
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
怎么玩
Explain+SQL语句
执行计划包含的信息
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
各个字段解释
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况
id相同,执行顺序由上至下
id不同,id值越大优先级越高,越先被执行,如果是子查询,id的序号会递增
id相同不同,同时存在
id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。(衍生=DERIVED)
select_type
有哪些
SIMPLE PRIMARY SUBQUERY DERIVED UNION UNION RESULT
查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询
1.SIMPLE
简单的select查询,查询中不包含子查询或者UNION
2.PRIMARY(当对于不是简单的查询的时候,他最外层肯定是PRIMARY,但是这个最外层的id级别只有一种,也就是1,但是他可以有几个1,但是他们的查询类型都是PRIMARY,看下面的例子)。也就是最后加载的复杂查询语句
查询中若包含任何复杂的子部分,最外层查询则被标记为(注意是最外层的,也就是说只有一个)
explain select t2.* from(select t3.id form t3 where t3.other_colum = '') s1,t2 where s1.id = s2.id (这个语句在查询s1,t2的时候id为1,select_type为PRIMARY,都是最外层查询)
3.SUBQUERY(在其他的查询里面他是子查询语句)
在SELECT或者WHERE列表中包含了子查询
4.DERIVED(该查询类型,查询出的结果集会被放到一个临时表中。注意:是在FROM列表中的子查询,被用于临时表)(这里是FROM列表中的子查询,这个字查询类型才是DERIVED)
在FROM列表中包含的子查询被标记为DERIVED(衍生)。MySQL会递归执行这些子查询,把结果放在临时表里。
5.UNION(UNION之后的select)
若第二个SELECT出现在UNION之后,则被标记为UNION;
若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
6.UNION RESULT
从UNION表获取结果的SELECT
table
显示这一行的数据是关于哪张表的
type
|ALL|index|range|ref|eq_ref|const,system|NULL|
子主题
访问类型排列
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
显示查询使用了何种类型
从最好到最差依次是:
system>const>eq_ref>ref>range>index>ALL
system(表中只有一条记录)
表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计
const(在查询时,where后面的条件使用的是primary key或者unique索引,并且只有一条数据与他匹配)(这里必须是唯一索引或者主键作为过滤条件,因此它所对应的结果集是唯一的,这个必须是使用“=”作为过滤条件,这样才能只匹配一条数据)(***奇怪***貌似唯一约束(应该是唯一约束决定的)+普通索引也是可以的,这里更准确的说应该是唯一的值)
表示通过索引一次就找到了,const用于比较primary key或者unique索引(唯一索引)。因为只匹配一行数据(注意,他只匹配一条数据),所以很快。如将主键至于where列表中,MySQL就能将该查询转换为一个常量(如果这个是主键,这条查询变成常量了)(这里和system的要注意,他是查询条件变成常量时)
explain select * from (select * from t1 where id=1 )d1 这里t1为const类型,他会根据id(为主键)去找,并且表中只有唯一的一条记录与他匹配。d1表中只有一条记录,所以为system
explain select * from test where address = 'a'and id =1 ;这里id为主键,address未建索引,对于这种,他会直接优化,当其中一个条件为主键或者唯一时,他会直接根据这一个条件(其他条件不管了)去查询。查询类型也为const。
若使用了主键或唯一索引查找值,发现没有对应的存在值,那么他会变为null,并打印为no matching row in const table。
eq_ref(必须和另外一个表(可以是本表,也可以是其他表)连接起来用,但是连接的字段必须是唯一约束或者主键)(这里是本表(主键或唯一索引)依赖其他表)
唯一性索引,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描(这里是以某个表为基准,然后查另外个表,另外的表中仅有唯一的记录与之匹配)
explain select * from t1,t2 where t1.id =t2.id 执行时是先全表扫描t2,这时拿到t2.id,t2扫描类型为all,然后根据t2扫描数据去t1中找,其中id为主键,t1查找时为eq_ref
(比较好的解释-MySQL手册是这样说的:"对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了 const 类型。它用在一个索引的所有部分被联接使用并且索引是 UNIQUE 或 PRIMARY KEY"。eq_ref 可以用于使用=比较带索引的列。)
ref(也就是说这里的查询条件没有使用主键或唯一索引,所以返回的行数可能为多条)(但是他这里的过滤条件必须是使用了索引)(如果是有两个过滤字段,其中一个字段是索引字段,也算为ref,同时这里需用使用“=”作为过滤条件)
非唯一索引扫描,返回匹配某个单独值的所有行。
本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
与eq_ref区别这个与记录匹配的数量可以是多行。
create index idx_col1_col2 on t1(col1,col2) 其中col1在表中不是唯一的
explain select * from t1 where col1 = "ac" 这里为ref类型
(不像 eq_ref 那样要求连接顺序,也没有主键和唯一索引的要求,只要使用相等条件检索时就可能出现,常见与辅助索引的等值查找。或者多列主键、唯一索引中,使用第一个列之外的列作为等值查找也会出现,总之,返回数据不唯一的等值查找就可能出现)
range(限定了范围)(必须是索引中的字段限定范围)
只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引
一般就是在你的where语句中出现了between、<、>、in等的查询
这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束语另一点,不用扫描全部索引
explain select * from t1 where id between 30 and 60;
explain select * from t1 where id in(1,2,6);
像这种在查询条件中限定范围的,他比全表扫描要好。
index
Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
explain select id from t1 其中id是主键,也就说他查询的值只需要是索引的值,但index是从索引中读取的。注意查询的结果集必须是全部都是索引里面的字段(可以来自两个索引的不同字段)
all
FullTable Scan,将遍历全表以找到匹配的行
备注:
一般来说,得保证查询只是达到range级别,最好达到ref
*********
补充:这里会产生使用主键或者唯一索引列作为过滤条件时,type为null的情况。
产生“Impossible WHERE noticed after reading const tables”的原因是这样的,当在查询语句中存在满足如下条件的 WHERE 语句时,MySQL在 EXPLAIN 之前会优先根据这一条件查找出对应的记录,并用记录的实际值替换查询中所有使用到的该表属性。
这是因为满足以下四个条件时,就会使得针对该表的查询最多只能产生一条命中结果。在该表无法命中数据的情况下就会提示“在 const table 表中没有找到匹配的行”,而这个 “const table”就指的是满足下面四个条件的表。这是 MySQL 的一个优化策略。
当查询条件中包含了某个表的主键或者非空的唯一索引列
该列的判定条件为等值条件
目标值的类型与该列的类型一致
目标值为一个确定的常量
*********
possible_keys(哪些索引可以使用)
显示可能应用在这张表中的索引,一个或多个。
查询涉及的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key(哪些索引被实际使用)
实际使用的索引。如果为null则没有使用索引
查询中若使用了覆盖索引,则索引和查询的select字段重叠
注意当查询字段和复合索引中字段的个数和顺序一一一致时,会出现不存在possible_keys,但是有key值得情况,就是他认为没有条件过滤的时候,去全表扫描获取值(理论上认为不会用到索引),但是这些值能在索引中找到,所以实际上就用了索引
key_len(同样查询结果下,精度越小越好。精度小的话,损耗的key_len越小)
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len显示的值为索引最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
ref(表之间的引用)
显示索引那一列被使用了,如果可能的话,是一个常数。那些列或常量被用于查找索引列上的值
explain select * from t1,t2 where t1.col1 = t2.col1 and t1.col2 ='ac';
---------------------------------------------------------------------------
|id|...|table|type|possible_keys|key |key_len|ref |rows|
---------------------------------------------------------------------------
|1 |...|t2 |ALL |NULL |NULL |NULL |NULL |640 |
|1 |...|t1 |ref |idx_col1_col2|idx_col1_col2|26 |share.t2.col1,const|82 |
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即'ac'。
rows(每张表有多少行被优化器查询)
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
explain select * from t1,t2 where t1.id = t2.id and t2.id ='ac';
--------------------------------------------------------------------------------------------+
|id|select_type|table|type |possible_keys|key |key_len|ref |rows|Extra |
--------------------------------------------------------------------------------------------+
|1 |SIMPLE |t2 |ALL |PRIMARY |NULL |NULL |NULL |640 |Using where|
|1 |SIMPLE |t1 |eq_ref |PRIMARY |PRIMARY |4 |shared.t2.ID|1 | |
create index idx_col1_col2 on t2(col1,col2);
explain select * from t1,t2 where t1.id = t2.id and t2.id ='ac';
-------------------------------------------------------------------------------------------+
|id|select_type|table|type |possible_keys |key |key_len|ref |rows|
-------------------------------------------------------------------------------------------+
|1 |SIMPLE |t2 |ref |PRIMARY,idx_col1_col2|idx_col1_col2|195 |const |142 |
|1 |SIMPLE |t1 |eq_ref |PRIMARY |PRIMARY |4 |shared.t2.ID|1 |
Extra
包含不适合在其他列中显示但十分重要的额外信息
1.Using filesort(需要优化,九死一生)
说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作成为“文件排序”
explain select col1 from t1 where col1 = 'ac' order by col3 (在Extra中:using where;using index;using filesort)(存在索引idx_col1_col2_col3)查询方面用到了索引,但是排序方面没用到
explain select col1 from t1 where col1 = 'ac' order by col1,col2,col3(在Extra中:using where;using index)查询和排序都用到了索引
当使用了order by时,如果后面的字段不是按照索引字段的顺序来的会出现Using filesort情况,也就是说order by后面的字段要按照索引顺序来,可以少末尾的字段,但是要按照顺序来,就像楼梯一样,例如idx_col1_col2_col3可以是(col1),(col1,col2)和(col1,col2,col3)
如果索引没失效的话只要按照顺序来也是可以让他使用到索引的 例如where col1 = 'ac' order by col2,col3,他也是使用到了索引的。col1 = 'ac'没有让col1失效,col2,col3也是按照顺序来。
如果前面的where条件使得失去使用索引,那么后面的order by必须按照索引顺序来。也就是说对于order by后面的,只要是使用到了索引(有两种,一种是重新使用整个索引col1,col2,col3,一种是接着where条件中的索引来where col1 = 'ac' order by col2,col3)都可以规避Using filesort问题
2.Using temporary(必须优化)
使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by(不过这里的order by没按照索引字段的顺序排序的话没出现Using temporary,出现了Using filesort) 和分组查询 group by(统计分析)
explain select col1 from t1 where col1 in('ac','ab','aa') group by col2 (using where;using index;using temporary;using filesort)(存在索引idx_col1_col2)
explain select col1 from t1 where col1 in('ac','ab','aa') group by col1,col2 (using where;using index for group-by)
当使用了生成临时表时(例如group by),最后排序的顺序要按照索引的顺序来,例如索引为idx_col1_col2,那么就要使用group by col1,col2。这个和上面的order by是一样的。按照索引顺序来,可以少末尾的字段,但是要按照顺序来,就像楼梯一样,例如idx_col1_col2_col3可以是(col1),(col1,col2)和(col1,col2,col3)
如果索引没失效的话只要按照顺序来也是可以让他使用到索引的 例如where col1 = 'ac' group by col2,col3,他也是使用到了索引的。col1 = 'ac'没有让col1失效,col2,col3也是按照顺序来。
如果前面的where条件使得失去使用索引,那么后面的group by必须按照索引顺序来。也就是说对于group by后面的,只要是使用到了索引(有两种,一种是重新使用整个索引col1,col2,col3,一种是接着where条件中的索引来where col1 = 'ac' group by col2,col3)都可以规避Using temporary问题
3.USING index(可以使用)
表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表面索引用来读取数据而非执行查找动作。
覆盖索引(Covering Index) (一说为索引覆盖)
理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,
那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。
注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可使用select *,因此如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
4.Using where
表面使用了where过滤
5.using join buffer(连接过多会出现问题)
使用了连接缓存
6.impossible where (就是条件返回值恒为false:explain select * from staffs where name = 'aaa' and name = 'dd')
where子句的值总是false,不能用来获取任何元组
7.select tables optimized away
在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
8.distinct
优化distinct,在找到第一匹配的元组后即停止找同样值的工作
热身Case
索引优化
索引分析
单表
建表SQL
create table if not exists 'article'(
'id' int(10) unsigned not null primary key auto_increment,
'author_id' int(10) unsigned not null,
'category_id' int(10) unsigned not null,
'views' int(10) unsigned not null,
'comments' int(10) unsigned not null,
'title' varbinary(255) not null,
'content' TEXT not null
);
insert into 'article'('author_id','category_id','views','comments','title','content')values(1,1,1,1,'1','1'),(2,2,2,2,'2','2'),(3,3,3,3,'3','3');
select * from article;
案例
①原始情况:
#查询category_id为1且comments大于1的情况下,views最多的author_id。
explain select id author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
#结论,在这种没有创建索引的情况下,显然type为all,即最坏的情况。extra里还出现了using filesort,也是最坏的情况。优化是必须的。
②建索引的错误示范种类:
此时创建索引:
create index idx_article_ccv on article(category_id,comments,views);
explain select id author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
#结论,在这种创建索引的情况下,type为range,这是可以忍受的。但extra里出现了using filesort,仍是无法接受的,优化是必须的。
#但是我们已经建立了索引,为啥没用呢?
这是因为按照BTree索引的工作原理,先排序category_id,如果遇到相同的category_id则在排序comments,如果遇到相同的comments则在排序views。
当comments字段在联合索引里处于中间位置时,因comments > 1条件是一个范围值(所谓range)。所以会出现extra里出现了using filesort,views没使用到索引。
#*****MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。
若在这种索引(category_id,comments,views)下执行:
explain select id author_id from article where category_id = 1 and comments = 1 order by views desc limit 1;
#结论,在这种创建索引的情况下,type为ref,extra为using where,就是要达到这种效果(可是改变了需求)。
③针对这次需求正确建立索引:
删除第一次建立的索引:
drop index idx_article_ccv on article;
新建第二次索引(正确建立的):
alter table 'article' ADD INDEX idx_article_cv('category_id','views');
或create index idx_article_cv on article(category_id,views);
explain select id author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
#结论:可以看到,type变为了ref,extra中的Using filesort也消失了,结果理想。
两表
建表SQL
create table if not exists class(
id int(10) unsigned not null auto_increment,
card int(10) unsigned not null,
primary key(id)
);
create table if not exists book(
bookid int(10) unsigned not null auto_increment,
card int(10) unsigned not null,
primary key(bookid)
);
案例
#explain分析:explain select * from class left join book on class.card = book.card;
#结论:type都是all
①现在添加索引优化:
alter table book add index Y('card')
第二次执行explain:
explain select * from class left join book on class.card = book.card;
#可以看到第二行的type变为了ref,rows也变成了优化比较明显。
#这是由左连接特性决定的。left join条件用于确定如果从右表搜索行,左边一定都有,所以右边是关键点,一定需要建立索引。
②第二种建索引情况:
删除旧索引+新建+第三次explain
DROP INDEX Y ON book;
ALTER TABLE class ADD Index X(card);
explain select * from class left join book on class.card = book.card;
此时type为index(对于class表,主表),index不如ref。rows未优化,这个性能并不好,仅仅是主表进行了优化,但是没啥意义。
#然后来看一个右连接查询:
#优化较明显。这是因为right join条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。
explain select * from class right join book on class.card = book.card;
此时主表为book,对class从表进行了优化,class的type为ref,优化明显。
若此时:
DROP INDEX X ON class;
ALTER TABLE book ADD Index Y(card);
#右连接,基本无变化
explain select * from class right join book on class.card = book.card;
此时book有索引,主表是book,没啥优化。
总而言之:对于两个表的外连接关联查询,一般是对从表进行索引优化,主表作为基准,在关联方面,对主表进行优化没多大影响。需要对从表进行优化,使得从表的type为ref。
三表
建表SQL(在class,book表的前提下)
create table if not exists phone(
phoneid int(10) unsigned not null auto_increment,
card int(10) unsigned not null,
primary key(phoneid)
)
案例
在任何索引都没建立的情况下:
explain select * from class inner join book on class.card = book.card inner join phone on book.card = phone.card;
type全部为all,extra在book,phone这两个从表中有使用了缓存,extra为using join buffer。
在建立了以下索引的情况:
alter table phone add index z(card);
alter table book add index Y(card);
explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
优化后结果:book,phone的type为ref,book依赖class.card,phone依赖book.card。book,phone的extra为using index。
#后2行的type都是ref且总rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
【结论】
Join语句的优化
尽可能减少Join语句中的NestedLoop的循环总次数;“永远用小结果集驱动大的结果集”。
优先优化NestedLoop的内层循环;
保证Join语句中被驱动表上Join条件字段已经被索引;
当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝啬JoinBuffer(配置文件可以设置)的设置;
索引失效(应该避免) (或未用到索引)
建表SQL
create table staffs(
id int(10) primary key auto_increment,
name varchar(24) not null default '' comment '姓名',
age int(10) not null default 0 comment '年龄',
pos varchar(24) not null default '' comment '职位',
add_time TIMESTAMP not null default CURRENT_TIMESTAMP COMMENT '入职时间',
)CHARSET utf8 COMMENT '员工记录表'
alter table staffs add index idx_staffs_nameAgePos(name,age,pos);
案例(索引失效)
1.全值匹配我最爱
explain select * from staffs where name = '1';
使用到了索引,type为ref,ref为const。
explain select * from staffs where name = '1' and age =1;(在这里where age =1 and name = '1'也是一样的,也就是说在表达式中的索引条件顺序不会影响索引的使用,但是所拥有的索引字段顺序会影响,也就是表达式顺序不要紧)
使用到了索引,type为ref,ref为const,const。
explain select * from staffs where name = '1' and age =1 and pos = '1';
使用到了索引,type为ref,ref为const,const,const。
explain select * from staffs where age =1 and pos = '1';
type为all,未使用到索引,在where条件中,使用索引的前提是索引字段必须按照定义时的顺序存在,
也就是某个索引被使用要求where中存在定义索引的开头字段(例如idx_staffs_nameAgePos(name,age,pos)的name),若要使用了后续的字段(例如pos),必须要求前面的字段都使用到,就像一条火车一样。
2.最佳左前缀法则
如果索引了多例,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。(也就是说索引的某个字段被使用,要求前面的字段也被使用,否则就像火车断节就后续的字段无法使用)
3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
explain select * from staffs where name = 'July';
type为ref,ref为const,使用到了索引
explain select * from staffs where left(name,4) = 'July';
这里type为all,name未使用到索引,由于对索引字段name使用了left函数。
4.存储引擎不能使用索引中范围条件右边的列
explain select * from staffs where name = '1' and age = 1 and pos = '1';
type为ref,ref为const,const,const,使用到了索引的name,age,pos三个字段。
explain select * from staffs where name = '1' and age > 1 and pos = '1';(在sql中where所使用的索引字段顺序位置不会影响索引的使用,也就是和where age > 1 and name = '1' and pos = '1'结果是一样的)
type为range,在这里使用到了索引字段name,age(rang代表使用了age)。name用于查询,age是范围,age用于去排序。索引字段(非同一个索引字段是不会的)为范围后的索引字段都会失效(例如add_time过滤字段为范围值在name之后,age之前,是不会导致age失效的)
5.尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select*
explain select * from staffs where name = '1' and age = 1 and pos = '1';
type为ref,ref为const,const,const,使用到了索引的name,age,pos三个字段。extra为using where。
explain select name,age,pos from staffs where name = '1' and age = 1 and pos = '1';
type为ref,ref为const,const,const,使用到了索引的name,age,pos三个字段。extra为using where,using index(使用这个可以直接使用索引值,不需要去查表中的记录信息)。
注意:
explain select name,age,pos from staffs where name = '1' and age > 1 and pos = '1';
测试为:type为range,如果在查询的结果集中的字段都是所用索引的字段,并且查询不需要使用真实表的字段(也就是可以通过索引直接定位),这时Extra为Using index。
总而言之,如果查询列表的字段可以是某个索引中的字段就使用该索引中的字段,能从索引中获取实际值就只查索引,不去查询真实表的数据。
6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
explain select * from staffs where name ='1';
type为ref,使用上了索引,索引字段为name。
explain select * from staffs where name !='1';或explain select * from staffs where name <>'1';
type为all,未使用上索引,!=或者<>导致name在索引上面失效了。
7.is null,is not null 也无法使用索引
explain select * from staffs where name is null;
type为null,table及其他的属性都为null
explain select * from staffs where name is not null;
type为all,possible_key存在值,但是实际上key为null,也就是说实际上没有使用到索引。
因此我们在定义表的时候,尽量设置默认值,不要有null的存在,这会影响性能。
8.like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
explain select * from staffs where name ='%July';和explain select * from staffs where name ='%July%';
以上两者type为all,都是全表扫描,possible_key为null,key为null。
explain select * from staffs where name ='July%';
type为range,是用到了索引,possible_key和key为索引值。
问题:解决like'%字符串%'索引不被使用的方法??(简单的说:使用覆盖索引)
create table tbl_user(
id int(11) not null auto_increment,
name varchar(20) default null,
age int(11) default null,
email varchar(20) default null,
primary key(id)
)
在没建索引的情况下(这是为了和覆盖索引进行对比)
explain select name,age from tbl_user where name ='%aa%';
explain select id from tbl_user where name ='%aa%';
explain select name from tbl_user where name ='%aa%';
explain select age from tbl_user where name ='%aa%';
explain select id,name from tbl_user where name ='%aa%';
explain select id,name,age from tbl_user where name ='%aa%';
explain select name,age from tbl_user where name ='%aa%';
explain select * from tbl_user where name ='%aa%';
explain select id,name,age,email from tbl_user where name ='%aa%';
以上type都为all,都是全表扫描。
使用覆盖索引情况下:
创建索引create index
create index idx_user_nameAge ON tbl_user(name,age);
explain select name,age from tbl_user where name ='%aa%';(type为index,使用到了idx_user_nameAge索引,索引字段为name,age)
explain select id from tbl_user where name ='%aa%';(type为index,使用到了主键索引,索引字段为id)
explain select name from tbl_user where name ='%aa%';(type为index,使用到了idx_user_nameAge索引,索引字段为name)
explain select age from tbl_user where name ='%aa%';(type为index,使用到了idx_user_nameAge索引,索引字段为age)
explain select id,name from tbl_user where name ='%aa%';
explain select id,name,age from tbl_user where name ='%aa%';
explain select name,age from tbl_user where name ='%aa%';
以上三个都是type为index,使用到了idx_user_nameAge索引或者主键索引,索引字段为查询列表中的内容
(这里需要注意,对于这个覆盖索引,按理来说只能使用一个索引,但是对于查询列表结果集的时候,在使用的时覆盖索引可以是多个索引同时一起使用)
explain select * from tbl_user where name ='%aa%';
explain select id,name,age,email from tbl_user where name ='%aa%';
以上两个type为all,由于查询的字段值超出了覆盖索引范围,所以没用上索引。
(在where条件过滤方面,最多只能使用一个索引,也就是key)
9.字符串不加单引号索引失效
MySQL在底层会隐式自动的做一次类型转换。
explain select * from staffs where name ='2000'; name类型本为varchar
type为ref,使用到了索引。
explain select * from staffs where name =2000;
type为all,存在理论上使用到的索引,实际上没使用索引。
10.少用or,用它连接时会索引失效
explain select * from staffs where name ='July' or name ='z3';
type为all,possible_keys存在值,key为null。
11.小总结
假设index(a,b,c)
|where语句 |索引是否被使用 |
|where a=3 |Y,使用到了a |
|where a=3 and b=5 |Y,使用到了a,b |
|where a=3 and b=5 and c=4 |Y,使用到了a,b,c |
|where b=3 或者where b=3 and c=4 或者where c=4|N |
|where a=3 and c=5 |使用到a,但是c不可以,b中间断了 |
|where a=3 and b>4 and c=5 |使用到a和b,c不能用在范围之后,b断了 |
|where a=3 and b like 'kk%' and c=4 |a能用,b能用,c不能用 |
like KK%相当于=常量 %KK和%KK% 相当于范围
*****
索引可以用于查询和排序这两个功能
*****
面试题讲解
题目SQL
create table test03(
id int primary key not null auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10)
);
创建索引:
create index idx_test03_c1234 on test03(c1,c2,c3,c4);
在创建复合索引的idx_test03_c1234的情况下分析SQL使用情况
explain select * from test03 where c1 = 'a1';
explain select * from test03 where c1 = 'a1' and c2 = 'a2';
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 = 'a3';
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 = 'a3' and c4 = 'a4';
以上type都为ref,都是全值匹配。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 = 'a3' and c4 = 'a4';
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c4 = 'a4' and c3 = 'a3';
explain select * from test03 where c4 = 'a4' and c3 = 'a3' and c2 = 'a2' and c1 = 'a1';
(explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 > 'a3' and c4 = 'a4';和 explain select * from test03 where c3 > 'a3' and c2 = 'a2' and c1 = 'a1' and c4 = 'a4';这里c3的顺序虽然不同,但是执行explain的结果是一样的)
前面三条执行结果是一样的,当调换其中where条件的顺序的时候,并不会影响结果,即使其中条件是使用了><这种范围过滤,也不会影响explain执行结果。
MySQL会开启查询优化器optimizer会将MySQL的命令进行自动的调整和优化(当写SQL的时候最好还是按照SQL实际执行过程来,少让优化器进行优化,可以减少优化器的翻译和转换)
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 > 'a3' and c4 = 'a4';
type为range,索引中c1和c2用于查询,c3用于排序。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c4 > 'a4' and c3 = 'a3';
type为range,索引中c1,c2和c3用于查询,c4用于排序。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c4 = 'a4' order by c3;
type为ref,索引字段c1,c2(都是定值)用于查找,c3作用在排序而不是查找。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' order by c3;
和上面一个是一样的,其实我们可以把where,order by,group by分成三块来看。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' order by c4;
type为ref,索引字段c1,c2(都是定值)用于查找,c4并没有用于排序,为了满足order by c4,在extra中出现using filesort用于排序。
explain select * from test03 where c1 = 'a1' and c5 = 'a5' order by c2,c3;
type为ref,只用了索引字段c1(是定值)用于查找,c2,c3用于排序。(索引显示是使用到了c1字段,严格来说c2,c3也算是使用到了该索引字段,只不过他是用于排序)无using filesort。
explain select * from test03 where c1 = 'a1' and c5 = 'a5' order by c3,c2;
type为ref,只用了索引字段c1(是定值)用于查找,c2,c3没有用于排序。出现了using filesort。索引顺序是12345,这里的order by为32,所以没用上。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' order by c2,c3;
type为ref,索引字段c1,c2(都是定值)用于查找,其中由于c2为定值,在order by后面c2都是一样的,所以c3是排序,无using filesort,好像c2在后面用于排序,其实他已经是个定值了,没起任何作用,相当于是order by c3。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c5 = 'a5' order by c2,c3;
这个和上面的一个是一样的,c5 = 'a5'并不会有啥影响。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c5 = 'a5' order by c3,c2;(原则上order by后面是用于排序,如果索引不能满足整个order by,他排序这个操作不会使用索引)
type为ref,索引字段c1,c2(都是定值)用于查找,无using filesort,在order by后面c3直接用于排序,对于c2,在前面已经作于一个定值,对排序不会起作用了(对于这个做个小总结:如果在where条件后面出现了一些定值,分析对于order by后面排序字段去掉前面定值对应的变量,因为这些变量不会影响排序)。
explain select * from test03 where c1 = 'a1' and c4 = 'a4' group by c2,c3;
type为ref,索引字段c1用于查找,extra为using where性能很好。(这里索引其实已经用于group by c2,c3了,也就是说group by是会使用到索引的,具体是啥不是很清楚,可以看成类似order by)
explain select * from test03 where c1 = 'a1' and c4 = 'a4' group by c3,c2;
type为ref,索引字段c1用于查找,extra为Using temporary,using filesort必须优化,可以看出索引并没有用于group by c3,c2(可以看成order by类似)
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c4 = 'a4' group by c3,c2;
type为ref,索引字段c1,c2用于查找,extra为using where性能很好,索引字段c3已经用于group by了,c2也是个定值,这样看来分组和c2没关系。这点也可以看成和order by类似(对于这个做个小总结:如果在where条件后面出现了一些定值,分析对于group by后面排序字段去掉前面定值对应的变量,因为这些变量不会影响分组)。
group by表面上叫分组,分组之前必排序。所以说group by和order by其排序的法则和索引优化的原则几乎是一致的(存在不一致的地方是having)
定值、范围还是排序,一般order by是给个范围(索引作用:查找和排序)
group by 基本上都需要进行排序,会有临时表产生(group by分组都会进行排序,如果错乱了(未排序)以后会导致临时表产生(相当于内部需要重新生成一个表进行排序在分组))
一般性建议
对于单键索引,尽量选择针对当前query过滤性更好的索引
在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
在选择组合索引的时候,尽量选择可以能包含当前query中的where子句中更多字段的索引
尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
**********************
小补充:where 条件中如果存在c1 like 'a%'这种,type会变为range,可以直接把like当成范围(范围虽然使用到了索引,但是会导致后面的索引字段(定义时的)失效),但是c1还是用上了索引。如果是c1 like '%a%'(会导致索引失效)就c1完全没用上索引。
依旧是test03的条件下,
explain select * from test03 where c1 = 'a1' and c2 like 'kk%' and c3 = 'a3';
type为range,索引字段c1用于查找,c2使得type为range,c2使用到了索引,其中key_len长度为93(但是如果去掉c3 = 'a3'长度为62,也就是说其实c3也是使用到了索引的,这点和><是不同,like不会导致后面的索引字段(定义时)失效,><会导致失效)
explain select * from test03 where c1 = 'a1' and c2 > 'a2' and c3 = 'a3';
type为range,索引字段c1用于查找,c2使得type为range,c2使用到了索引用于排序,其中key_len长度为62,没有使用到索引字段c3,说明><会导致索引字段失效(定义时)。
explain select * from test03 where c1 = 'a1' and c2 like '%kk%' and c3 = 'a3';
type为ref,仅仅使用到了c1索引字段,key_len为31,like '%kk%'会导致与之对应的变量的字段失效,也会导致后面的索引字段(定义时)失效。
explain select * from test03 where c1 = 'a1' and c2 like 'k%kk%' and c3 = 'a3';
这个和like 'kk%'是一样的
**********************
**********************
分析存在问题的SQL:
1 观察,至少跑1天,看看生产的慢SQL情况。
2 开启慢查询日志,设置阙值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
3 explain+慢SQL分析
4 show profile
5 运维经理 or DBA,进行SQL数据库服务器的参数调优。
总结:
1 慢查询的开启并捕获
2 explain+慢SQL分析
3 show profile查询SQL在MySQL服务器里面的执行细节和生命周期情况
4 SQL数据库服务器的参数调优
**********************
3查询截取分析
查询优化
永远小表驱动大表类似嵌套循环Nested Loop
Case
一般写循环语句的时候,内循环大于外循环次数要优于外循环大于内循环。(他少了内外层循环之间的交替)
优化原则:小表驱动大表,即小的数据集驱动大的数据集。
①:select * from A where id in(select id from B)
等价于:
for select id from B
for select * from A where A.id = B.id
当B表的数据集必须小于A表的数据集时,用in优于exists
②:select * from A where exists(select 1 from B where B.id = A.id)
等价于
for select * from A
for select * from B where B.id = A.id
当A表的数据集系小于B表的数据集时,用exists优于in。
EXISTS
select ... from table where EXISTS(subquery)
该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。
1 EXISTS(subquery)只返回TRUE或FALSE,因此子查询中的select *也可以是select 1或select 'X'官方说法是实际执行时会忽略select清单,因此没有区别。
2 EXISTS子查询的实际执行过程可能经过了优化而不是我们理解的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
3 EXISTS子查询往往也可以用条件表达式,其他子查询或者join来替代,何种最优需要具体问题具体分析。
order by关键字优化
ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
建表SQL
create table tbla(
id int primary key not null auto_increment,
age int,
birth TIMESTAMP not null
);
create index idx_A_ageBirth ON tbla(age,birth)
Case
1
现在分析的是order by后面的排序是否产生filesort。(用于1,2实验结论)
explain select * from tbla where age > 20 order by age
type为index,由于这里只有三个字段,一个是主键,另外两个是索引字段,所以用到了覆盖索引,所以type为index。extra使用到了using index,由于age在where上为范围值,所以order by使用的索引字段要从头开始算。
explain select * from tbla where age > 20 order by age,birth
和上一个的结论相同。
explain select * from tbla where age > 20 order by birth
type为index,由于这里只有三个字段,一个是主键,另外两个是索引字段,所以用到了覆盖索引,所以type为index。extra使用到了using filesort,由于age在where上为范围值,所以order by使用的索引字段要从头开始算,这里是从birth开始算的,没用上索引,所以要使用filesort排序。
explain select * from tbla where age > 20 order by birth,age
type为index,由于这里只有三个字段,一个是主键,另外两个是索引字段,所以用到了覆盖索引,所以type为index。extra使用到了using filesort,由于age在where上为范围值,所以order by使用的索引字段要从头开始算,这里是从birth开始算的(要求order by后面的顺序是和定义时索引字段顺序一样的,相当于索引就是一个固定的表了),没用上索引,所以要使用filesort排序。
2
explain select * from tbla order by birth
type为index,由于这里只有三个字段,一个是主键,另外两个是索引字段,所以用到了覆盖索引,所以type为index。extra使用到了using filesort,order by在排序中并没有按照索引字段的顺序来(如果where中存在索引字段为定值,那么这个字段是不会影响排序的,可以直接认为该字段存在且满足排序)
explain select * from tbla where birth > '2016-01-28 00:00:00' order by birth
分析结论同上,这里也说明了,其实where和order by后面是分开看待的,他们并不会交叉干扰(只是where后的索引字段为定值,当成在order by中已经存在且满足排序)
explain select * from tbla where birth > '2016-01-28 00:00:00' order by age
type为index,由于这里只有三个字段,一个是主键,另外两个是索引字段,所以用到了覆盖索引,所以type为index。extra使用到了using index,order by后的age是从索引字段开头开始的,所以用到了索引。
explain select * from tbla order by age ASC,birth DESC
type为index,由于这里只有三个字段,一个是主键,另外两个是索引字段,所以用到了覆盖索引,所以type为index。extra使用到了using filesort,order by默认是升序,如果想order by后面的索引字段使用到索引,那么他们必须是同为升序或者同为降序。
MySQL支持二种方式的排序,FileSort和Index,Index效率高。它指MySQL扫描索引本身完成排序(也就是索引自带的顺序)。FileSort方式效率较低。
ORDER BY满足两情况,会使用Index方式排序
ORDER BY语句使用索引最左前列
使用where子句与OrderBy子句条件列组合满足索引最左前列(也就是where后面的索引字段为定值,则满足在排序中的索引字段存在且满足索引使用)
尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀(在索引中满足排序要求)
如果不在索引列上(也就是extra使用不到using index,那么他排序就要用filesort),filesort有两种算法:mysql就要启动双路排序和单路排序
双路排序
MySQL4.1之前是使用双路排序,字面意思是两次扫描磁盘,最终得到数据。读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据传输
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二张改进的算法,就是单路排序。
单路排序
从磁盘读取查询需要的所有列,按照orderby列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据,并且把随机IO变成顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
结论及引申出的问题
由于单路是后出来的,总体而言好过双路
但是用单路有问题
在sort_buffer中,方法B比方法A要占用很多空间,因为方法B是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再去取sort_buffer容量大小,再排......从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
优化策略
增大sort_buffer_size参数的设置
增大max_length_for_sort_data参数的设置
why
提高order by的速度
1.order by时select *是一个大忌,只query需要的字段,这点非常重要。在这里的影响是:
1.1当query的字段大小总和小于max_length_for_sort_data而且排序字段不是TEXT|BLOB类型时,会用改进后的算法--单路排序,否则用老算法--多路排序。
1.2两种算法的数据都有可能超出sort_buffer的容量,超过之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
2.尝试提高sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的
3.尝试提高max_length_for_sort_data
提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。
小总结
为排序使用索引:
MySQL两种排序方式:文件排序或扫描有序索引排序
MySQL能为排序与查询使用相同的索引
以KEY a_b_c(a,b,c)为例:
order by能使用索引最左前缀
-ORDER BY a
-ORDER BY a,b
-ORDER BY a,b,c
-ORDER BY a DESC,b DESC,c DESC
如果WHERE使用索引的最左前缀定义为常量,则order by能使用索引
-WHERE a = const ORDER BY b
-WHERE a = const AND b = const ORDER BY c
-WHERE a = const ORDER BY b,c
-WHERE a = const AND b > const ORDER BY b,c
不能使用索引进行排序
-ORDER BY a ASC,b DESC,c DESC /*排序不一致*/
-WHERE g = const ORDER BY b,c /*丢失a索引*/
-WHERE a = const ORDER BY c /*丢失b索引*/
-WHERE a = const ORDER BY a,d /*d不是索引的一部分*/
-WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/
GROUP BY关键字优化(几乎和order by优化一致)
groupby实质是先排序后进行分组,遵照索引建的最佳左前缀
当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
where高于having,能写在where限定的条件就不要去having限定了。
慢查询日志
是什么
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阙值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。
由他来查看哪些SQL超过了我们的最大忍耐时间值,比如一条SQL执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的SQL,结合之前explain进行全面分析。
怎么玩
说明
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
查看是否开启及如何开启
默认
SHOW VARIABLES LIKE '%slow_query_log%'
默认情况下slow_query_log的值为off,表示慢查询日志是禁用的,可以通过设置slow_query_log的值来开启(SHOW VARIABLES LIKE '%slow_query_log%')
开始
set global slow_query_log = 1
使用set global slow_query_log = 1开启了慢查询日志只对当前数据库生效,如果MySQL重启后则会失效。
如果要永久生效,就必须修改配置文件my.cnf(其他系统变量也是如此)
修改my.cnf文件,[mysqld]下增加或修改参数
slow_query_log和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/wuxi-slow.log
关于慢查询的参数slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)
那么开启慢查询日志后,什么样的SQL参会记录到慢查询里面?
这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,命令:SHOW VARIABLES LIKE 'long_query_time%';
可以使用命令修改,也可以在my.cnf参数里面修改。
假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,在MySQL源码里是判断大于long_query_time,而非大于等于。
Case
查看当前多少秒算慢
SHOW VARIABLES LIKE 'long_query_time%';
设置慢的阙值时间
set global long_query_time=3; 在设置完阙值时间后,使用SHOW VARIABLES LIKE 'long_query_time%'查询变量long_query_time的值,但是值并没有发生改变(这里需要重新开启一个会话或者重连客户端,不是重启服务端)
为什么设置后看不出变化?
需要重新连接或者新开一个回话才能看到修改值。(重启客户端,不是重启服务端)
SHOW VARIABLES LIKE 'long_query_time%';
show global variables like 'long_query_time';
记录慢SQL并后续分析
在开启了慢查询日志后,可以在slow_query_log_file对应的文件中查看相关的查询慢的SQL。
查询当前系统中有多少条慢查询记录
show global status like '%slow_queries%'
配置版
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/wuxi-slow.log
long_query_time=3
log_output=FILE
日志分析工具mysqldumpshow(在生产环境中,如果要手工分析日志,查找,分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpshow)
查看mysqldumpshow的帮助信息(使用mysqldumpshow --help)
s:是表示按何种方式排序
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:即为返回前面多少条的数据
g:后边搭配一个正则匹配模式,大小写不敏感的
工作常用参考
得到返回记录集最多的10个SQL
mysqldumpshow -s r -t 10 /var/lib/mysql/wuxi-slow.log
得到访问次数最多的10个SQL
mysqldumpshow -s c -t 10 /var/lib/mysql/wuxi-slow.log
得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpshow -s t -t 10 -g "left join" /var/lib/mysql/wuxi-slow.log
另外建议在使用这些命令时结合|和more使用,否则有可能出现爆屏情况
mysqldumpshow -s r -t 10 /var/lib/mysql/wuxi-slow.log|more
批量数据脚本
往表里插入1000W数据
1.建表
create table dept(
id int(10) unsigned primary key auto_increment,
deptno mediumint unsigned not null default 0,
dname varchar(20) not null default "",
loc varchar(13) not null default ""
)engine = innodb default charset=uft8;
create table emp(
id int(10) unsigned primary key auto_increment,
empno mediumint unsigned not null default 0,
ename varchar(20) not null default "",
job varchar(9) not null default ""
mgr mediumint unsigned not null default 0,
hiredate date not null,
sal decimal(7,2) not null,
comm decimal(7,2) not null,
deptno mediumint unsigned not null default 0,
)engine = innodb default charset=uft8;
2.设置参数log_trust_function_createors
创建函数,假如报错:this function has none of deterministic...
#由于卡其过慢查询日志,因为我们开启了bin-log,我们就必须为我们的function指定一个参数。
show variables like 'log_trust_function_createors';
set log_trust_function_createors=1;
#这样添加了参数以后,如果MySQL重启,上述参数又会消失,永久方法:
windows下my.ini[mysqld]加上log_trust_function_createors=1
Linux下 /etc/my.cnf下my.cnf[mysqld]加上log_trust_function_createors=1
3创建函数保证每条数据都不同
随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS varchar(255)
BEGIN
DECLARE chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str varchar(255) default '';
DECLARE i INT default 0;
WHILE i < n DO
SET return_str = CONCAT(return_str,SUBSTRING(chars_str,FlOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$
随机产生部门编号
#用于随机产生部门编号
DELIMITER $$
CREATE FUNCTION rand_num() RETURNS INT(5)
BEGIN
DECLARE i INT default 0;
SET i = FlOOR(100+RAND()*10);
RETURN i;
END $$
#假如要删除
#drop function rand_num;
4.创建存储过程
创建往emp表中插入数据的存储过程
DELIMITER $$
CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))
BEGIN
DECLARE i INT default 0;
#set autocommit = 0把autocommit 设置成0
set autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp(empno,ename,job,mgr,hiredate,sal,comm,deptno)VALUES((START+i),rand_string(10),rand_string(8));
UNTIL i = max_num;
END REPEAT;
COMMIT;
END $$
#删除
#DELIMITER ;
#drop PROCEDURE insert_emp;
创建往dept表中插入数据的存储过程
DELIMITER $$(这是设置分隔符号,需要注意下)
CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))
BEGIN
DECLARE i INT default 0;
#set autocommit = 0把autocommit 设置成0
set autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept(deptno,dname,loc)VALUES((START+i),rand_string(6),'SALESMAN',0001,CURDATE(),2000,400,rand_num());
UNTIL i = max_num;
END REPEAT;
COMMIT;
END $$
5.调用存储过程
dept
DELIMITER ;
CALL insert_dept(100,10);
emp
DELIMITER ;
CALL insert_emp(100001,500000);
Show profiles
是什么:是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优测量
官网:http://dev.mysql.com/doc/refman/5.5/en/show-profile.html
默认情况下,参数处于关闭状态,并保存最近15次的运行结果
分析步骤
1.是否支持,看看当前的SQL版本是否支持
show variables like 'profiling'或者show variables like 'profiling%'默认是关闭,使用前需要开启
2.开启功能,默认是关闭,使用前需要开启
show variables like 'profiling';
set profiling = on;
3.运行SQL
select * from emp group by id%10 limit 150000(这种写法只是让SQL执行的慢)
select * from emp group by id%20 order by 5
4.查看结果,show profiles;
5.诊断SQL,show profile cpu,block io for query (query为show profiles查询对应的id)上一步前面的问题SQL 数字号码;
参数备注
type:
|ALL --显示所有的开销信息
|BLOCK IO --显示块IO相关开销
|CONTEXT SWITCHES --上下文切换相关开销
|CPU --显示CPU相关开销信息
|IPC --显示发送和接受相关开销信息
|MEMORY --显示内存相关开销信息
|PAGE FAULTS --显示页面错误相关开销信息
|SOURCE --显示和source_function,source_file,source_line相关的开销信息
|SWAPS --显示交换次数相关开销的信息
6.日常开发需要注意的结论
converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
Creating tmp table 创建临时表(注意group by)
拷贝数据到临时表
用完再删除
Copying to tmp table on disk 把内存中临时表复制到磁盘,危险!!!
locked
全局查询日志
配置启用(开启服务器时就启用)
在MySQL的my.cnf中,设置如下:
#开启
general_log=1
#记录日志文件的路径
general_log_file=/path/logfile
#输出格式
log_output=FILE
编码启用(使用时启用,服务器重启后失效)
命令:
set global general_log=1;
set global log_output='TABLE';
此后,你所编写的SQL语句,将会记录到MySQL库里的general_log表,可以用下面的命令查看
select * from mysql.general_log;
永远不要在生产环境开启这个功能。
4Mysql锁机制
概述
定义
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(如CPU,PAM,I/O等)的争用以外,数据也是一种供许多用户共享的资源。
如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
生活购物
打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?
这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。
锁的分类
从数据操作的类型(读、写)分
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
从对数据操作的颗粒度
表锁
行锁
三锁
表锁(偏读)
特点
偏向MyISAM存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发最低
案例分析
建表SQL
create table mylock(
id int not null primary key auto_increment,
name varchar(20)
)engine myisam;
【手动增加表锁】
lock table 表名字1 read(write),表名字2 read(write),其他;(这是锁表的语句,read(write)是从读锁或者写锁中选一个,锁表的操作是针对一个会话来说的)
【查看表上加过的锁】
show open tables;他会显示所有会话对所有表的上锁情况,当有一个会话对某个表上锁,该表对应的In_use会加一,一个会话可以对多个表进行上锁,一个表也可以被多个会话上锁。
【释放表锁】
unlock tables;
加读锁
我们为mylock表加read锁(读阻塞写例子)
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|session_1 |session_2 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|获得表mylock的read锁定(lock table mylock read;) |连接终端 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|当前session可以查询该表记录(select * from mylock) |其他session也可以查询该表的记录(select * from mylock) |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|当前session不能查询其他没有锁定的表(连查都不能查,更不能更新了) |其他session可以查询或者更新未锁定的表 |
|select * from book;出现错误提示:'book' was not locked with lock tables|select * from book;可以得到对应的book表结果 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|当前session中插入或者更新锁定的表都会提示错误 |其他session插入或者更新锁定的表会一直等待获得锁 |
|insert into mylock(name) values('e');出现错误提示: |insert into mylock(name) values('e');此时无任何反应,处于等待状态 |
|Table 'mylock' was locked with a read lock and can't be updated | |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|释放锁(unlock tables;) |session2获得锁,插入操作完成:接着上面一行的插入语句,这条插入语句在session_1释放锁后执行成功|
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
加写锁
我们为mylock表加write锁(MyISAM存储引擎的写阻塞读例子)
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|session_1 |session_2 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|获得表mylock的write锁定(lock table mylock write;) |待session_1开启写锁后,session_2再连接终端 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|当前session对锁定表的查询+更新+插入操作都可以执行: |其他session对锁定表的查询被阻塞,需要等待锁被释放:(连查询都被阻塞了,其他修改操作也是阻塞)|
|select * from mylock where id = 1; |select * from mylock where id = 1; |
|update mylock set name = 'a2' where id = 1; |备注:如果可以,换成不同的id来进行测试,因为mysql有缓存,第二次的条件会从缓存获取, |
|insert into mylock(name) values('f'); |影响锁效果演示。 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|释放锁(unlock tables;) |session2获得锁,执行上面的SQL语句,查询返回(或者其他修改操作,进行表记录修改操作) |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|当前session不能查询其他没有锁定的表(连查都不能查,更不能更新了) |其他session可以查询或者更新未锁定的表 |
|select * from book;出现错误提示:'book' was not locked with lock tables|select * from book;可以得到对应的book表结果 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
案例结论
MyISAM在执行查询语句(select)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table write Lock)
+-------------------------------------------------------------+
|锁类型 |可否兼容 |读锁 |写锁 |
+-------------------------------------------------------------+
|读锁 |是 |是 |否 |
+-------------------------------------------------------------+
|写锁 |是 |否 |否 |
+-------------------------------------------------------------+
结论:
结合上表,所以对MyISAM表进行操作,会有以下情况:(以下都是针对整个表来说的)
1,对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作。(本进程只能引擎为MyISAM的表,并且本进程给表上了读锁,那么本进程对该表的操作只能是读取,不能对该表写入)
2,对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。(本进程只能引擎为MyISAM的表,并且本进程给表上了写锁,那么本进程对该表的操作既能是读取,也能对该表写入)
简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。
表锁分析
【查看哪些表被加锁了】
show open tables;
【如何分析表锁定】
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定:
SQL:show status like 'table%' 可以查到table_locks_waited和table_locks_immediate参数对应的值。
这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1;
table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况;
此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。
行锁(偏写)
特点
偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁
由于行锁支持事务,复习老知识
事务(Transation)及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
原子性(Atomic):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistency):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durability):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
并发事务处理带来的问题
更新丢失(Lost Update)
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同一java文件。每个程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏对(Dirty Reads)
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”。
一句话:事务A读取到了事务B已修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
不可重复读(Non-Repeatable Reads)
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变,或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性。
幻读(Phantom Reads)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
一句话:事务A读取到了事务B提交的新增数据,不符合隔离性。
多说一句:幻读和脏读有点类似,脏读是事务B里面修改了数据,幻读是事务B里面新增了数据。
事务隔离级别
“脏读”,“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
+----------------------------------------------------------------------------------------------------+
|读数据一致性及允许的并发副作用隔离级别|读数据一致性 |脏读|不可重复读|幻读|
+----------------------------------------------------------------------------------------------------+
|未提交读(Read uncommitted) |最低级别,只能保证不读取物理上损坏的数据|是 |是 |是 |
+----------------------------------------------------------------------------------------------------+
|已提交读(Read committed) |语句级 |否 |是 |是 |
+----------------------------------------------------------------------------------------------------+
|可重复读(Repeatable read) |事务级 |否 |否 |是 |
+----------------------------------------------------------------------------------------------------+
|可序列化(Serializable) |最高级别,事务级 |否 |否 |否 |
+----------------------------------------------------------------------------------------------------+
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关系数据并发访问的能力。
查看当前数据库的事务隔离级别:show variables like 'tx_isolation';(系统默认Repeatable read级别。在这个隔离级别下,在同一事务下,无论数据有没有提交,重新获取更新后的数据(被本次事务更新过的数据,但是未提交),这个数据是在这个事务中更新后的数据(未提交的,保证可重复读),这也就保证了可重复读。)
案例分析
建表SQL
create table test_innodb_lock(a int(11),b varchar(16))engine=innodb;
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_b_ind on test_innodb_lock(b);
行锁定基本演示
行锁定基本演示:(在事务隔离级别为:可重复读的前提下)
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|session_1 |session_2 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|set autocommit = 0; |set autocommit = 0; |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|更新数据但是不提交,没有手写commit |session_2中查询当a = 4时,b为4000(这是在执行完了session_1的update之后,他读取的数据还是已提交的数据区域的)|
|语句执行了,但是没提交进去,可以理解为处于未提交区域 |update test_innodb_lock set b = '4002' where a = 1; |
|update test_innodb_lock set b = '4001' where a = 4;(原先b为4000) |此时update被阻塞了,其中session_1还没commit, |
|此时没有执行commit,然后在查询a = 4时,b为4001 |这是由于行锁的缘故,在表中某一行的位置,最多只能有一条修改操作处于未提交状态 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|提交更新commit |在session_1提交了commit后,session_2解除阻塞,更新正常进行,此时b = '4002' where a = 1更新语句处于未提交状态|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |执行commit命令 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
这里需要注意的是:某个session在读取某一行记录(或者记录的集合),先从本session执行修改数据命令(未提交的命令,处于未提交区域),查看数据结果,若没有,则在已经已提交区域中查找数据结果。
commit命令就是将未提交区域修改的数据结果提交到已提交区域中。
所以在session_1中执行update test_innodb_lock set b = '4001' where a = 4(b原来为4000)后,
session_1提交之前:在session_1中,在a = 4在未提交区域存在对应的数据,若session_1获取a = 4数据时,他获取的是未提交区域数据b = '4001'。
session_1提交之前:在其他session中(在其他session查询该记录),当其他session获取a = 4对应的记录数据时,他只能获取到已未的b = 4000。(这里需要注意的是:在其他的session中,他所查询的数据都是在执行set autocommit = 0或者commit那一刻已提交区域中的数据,当然如果该session对应的未提交区域有该数据也就读对应数据)
session_1提交之前:在其他session中(在其他session修改该记录),由于行锁缘故,a = 4这条记录被锁住了,其他session修改(update test_innodb_lock set b = '4002' where a = 1;)会被阻塞。
经过session_1提交之后,session_1修改过的未提交区域数据会被刷入提交区域。
session_1提交之后:在其他session中(在其他session修改该记录),对应的阻塞会被解开,从而其他session的修改命令将对应的数据在未提交区域中修改。
读取数据总而言之:先读本session中未提交区域数据,在读最近一次set autocommit = 0或者commit更新后的提交区域数据。
无索引行锁升级为表锁
比如在where中使用到了类型装换会导致索引失效,没用上索引,这会导致多个行锁升级为表锁(也就是多个session即使处理的是同一个表不同的记录,也会处于一一阻塞状态)
间隙锁危害
间隙锁带来的插入问题
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|session_1 |session_2 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|set autocommit = 0; |set autocommit = 0; |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|update test_innodb_lock set b = a*20 where a > 1 and a < 5;执行完毕,但未提交 |阻塞产生,暂时不能插入 |
|原先表中是没有a = 2这条记录的 |insert into test_innodb_lock values(2,'200') |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|commit; |阻塞解除,插入语句执行完成(但是未提交,修改的数据写在未提交区域中) |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
【什么是间隙锁】
当我们用范围条件而不是相等条件检索数据,并请求共享或排它锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙”。
InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
【危害】
因为query执行过程中通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值对也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。
他锁定的仅仅是间隙对应的键值,当然在整个where后面范围内的数据都会被锁定,也就是where a > 1 and a < 5,满足这个条件的记录都会被锁定。
面试题:常考如何锁定一行
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|session_1 |session_2 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|set autocommit = 0; |set autocommit = 0; |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|begin; |阻塞产生,暂时不能修改数据 |
|select * from test_innodb_lock where a = 8 for update; |update test_innodb_lock set b = '8001' where a = 8; |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|commit; |阻塞解除,修改语句执行完成(但是未提交,修改的数据写在未提交区域中) |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
select xxx... for update会锁定查询到的结果集,也就是满足select查询出来的记录集合里的所有数据都会被锁定。
案例结论
InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。
但是,InnoDB的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
行锁分析
【如何分析行锁定】
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'InnoDB_row_lock%';
对各个状态量的说明如下:
InnoDB_row_lock_current_waits:当前正在等待锁定的数量;
InnoDB_row_lock_time:从系统启动到现在锁定总时间长度;
InnoDB_row_lock_time_avg:每次等待所花平均时间;
InnoDB_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
InnoDB_row_lock_waits:系统启动后到现在总共等待的次数;
对于这5个状态变量,比较重要的主要是
InnoDB_row_lock_time_avg(等待平均时长),
InnoDB_row_lock_waits(等待总次数)
InnoDB_row_lock_time(等待总时长)这三项。
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
优化建议
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
合理设计索引,尽量缩小锁的范围
尽可能较少检索条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离
页锁
子主题
开销和加锁时间界于表锁和行锁之间:会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
了解一下即可
开销、加锁速度、死锁、粒度、并发性能只能就具体应用的特点来说哪种锁更合适
5主重复制
复制的基本原理
slave会从master读取binlog来进行数据同步
三步骤+原理图
master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志时间,binary log events
slave将master的binary log ebents拷贝到它的中继日志(relay log)
slave重做中继日志中的时间,将改变应用到自己的数据库中。MySQL复制是异步的且串行化的
复制的基本原则
每个slave只有一个master
每个slave只能有一个唯一的服务器ID
每个master可以有多个salve
复制最大问题
延时
一主一从常见配置
mysql版本一致且后台以服务运行(还有所在的系统需要ping通,也就是网络互通)
主从都配置在【mysqld】结点下,都是小写
主机修改my.ini配置文件
1.【必须】主服务器唯一ID
server-id =1
2.【必须】启用二进制日志
log-bin=自己本地的路径/mysqlbin (例如:F:/mysql-8.0.15-winx64/data/mysqlbin) 安装目录/data/mysqlbin
3.【可选】启动错误日志
log-err=自己本地的路径/mysqlerr
log-err=D:/devSoft/MySQLServer5.5/data/mysqlerr (安装目录/data/mysqlerr)
4.【可选】根目录
basedir="自己本地路径" (安装目录)
basedir="D:/devSoft/MySQLService5.5/"
5.【可选】临时目录
tmpdir="自己的本地路径" (安装目录)
tmpdir="D:/devSoft/MySQLService5.5/"
6.【可选】数据目录
datadir="自己本地路径/Data/" (安装目录/Data/)
datadir="D:/devSoft/MySQLService5.5/Data/"
7.read-only=0
主机,读写都可以(代表主机可以读取数据和写入数据)
8.【可选】设置不要复制的数据库
binlog-lgnore-db=mysql (用于指定不想复制的数据库)
9.【可选】设置需要复制的数据
binlog-do-db=需要复制的主数据库名字 (用于指定需要复制的数据库)
从机修改my.cnf配置文件
【必须】从服务器唯一ID server-id =2(将server-id =1的注释掉,去掉server-id =2的注释)
【可选】启用二进制文件 log-bin=mysql-bin(设置成这个)
因修改过配置文件,请主机+从机都启动后台mysql服务
主机从机都关闭防火墙(防火墙就相当于一个服务或者线程,可以直接关闭)
windows手动关闭
关闭虚拟机linux防火墙service iptables stop(命令service iptables stop)
在Windows主机上简历账户并授权slave
GRANT REPLICATION SLAVE ON*.* TO 'zhangsan'@'从机器数据库IP' IDENTIFIED BY '123456'; (在客户端输入命令:GRANT REPLICATION SLAVE ON*.* TO '用户'@'从机器数据库IP‘ IDENTIFIED BY '密码';三个点:用户,从机器数据库IP,密码)
flush privileges;(这是刷新命令)
查询master的状态
show master status;
记录下File和Position的值 (File为二进制日志对应的文件,Position为指定File从哪行开始复制)
执行完此步骤后不再执行主服务器MySQL,防止主服务器状态值变化
在Linux从机上配置需要复制的主机
CHANGE MASTER TO MASTER_HOST='主机IP',MASTER_USER='zhangsan',MASTER_PASSWORD='123456',MASTER_LOG_FILE='File名字',MASTER_LOG_POS=Position数字;(命令:CHANGE MASTER TO MASTER_HOST='主机IP',MASTER_USER='这个为主机授权的用户名',MASTER_PASSWORD='主机授权需要的密码',MASTER_LOG_FILE='主机状态中File名字',MASTER_LOG_POS=主机状态中Position数字)
启动从服务器复制功能
start slave; (在从机输入该命令,启动从机)
show slave status\G (在从机执行该命令查看配置是否成功)
下面两个参数都是YES,则说明主从配置成功!
Slave_IO_Running:Yes
Slave_SQL_Running:Yes
主机新建库、新建表、insert记录,从机复制
如何停止从服务复制功能
stop slave;(在从机执行该命令,使从机停止)
注意:在从机连接到主机执行CHANGE MASTER TO MASTER_HOST.....命令时,如果出现报错1198,run stop slave first。需要停止从机服务(stop slave),然后从机重新连接主机(CHANGE MASTER TO MASTER_HOST.....),这里需要从主机中重新获取File和Position的值。重连都要重新获取这些值