_逻辑架构
Posted shark-Gao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了_逻辑架构相关的知识,希望对你有一定的参考价值。
第04章_逻辑架构
🏠个人主页:shark-Gao
🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉
🎉目前状况:23届毕业生,目前在某公司实习👏👏
❤️欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘
🖥️个人小站 :个人博客,欢迎大家访问
配套视频参考:MySQL数据库天花板–康师傅
1. 逻辑架构剖析
1.1 服务器处理客户端请求
首先mysql是典型的C/S架构,即Clinet/Server 架构
,服务端程序使用的mysqld。
不论客户端进程和服务器进程是采用哪种方式进行通信,最后实现的效果是:客户端进程向服务器进程发送一段文本(SQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。
那服务器进程对客户端进程发送的请求做了什么处理,才能产生最后的处理结果呢?这里以查询请求为 例展示:
下面具体展开如下:
1.2 Connectors
Connectors, 指的是不同语言中与SQL的交互。MySQL首先是一个网络程序,在TCP之上定义了自己的应用层协议。所以要使用MySQL,我们可以编写代码,跟MySQL Server 建立TCP连接
,之后按照其定义好的协议进行交互。或者比较方便的方法是调用SDK,比如Native C API、JDBC、php等各语言MySQL Connecotr,或者通过ODBC。但通过SDK来访问MySQL,本质上还是在TCP连接上通过MySQL协议跟MySQL进行交互
接下来的MySQL Server结构可以分为如下三层:
1.3 第一层:连接层
系统(客户端)访问 MySQL 服务器前,做的第一件事就是建立 TCP 连接。 经过三次握手建立连接成功后, MySQL 服务器对 TCP 传输过来的账号密码做身份认证、权限获取。
- 用户名或密码不对,会收到一个Access denied for user错误,客户端程序结束执行
- 用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限
TCP 连接收到请求后,必须要分配给一个线程专门与这个客户端的交互。所以还会有个线程池,去走后面的流程。每一个连接从线程池中获取线程,省去了创建和销毁线程的开销。
所以连接管理的职责是负责认证、管理连接、获取权限信息。
1.4 第二层:服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询
,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
在该层,服务器会解析查询
并创建相应的内部解析树
,并对其完成相应的优化
:如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。
如果是SELECT语句,服务器还会查询内部的缓存
。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
-
SQL Interface: SQL接口
- 接收用户的SQL命令,并且返回用户需要查询的结果。比如SELECT … FROM就是调用SQL Interface
- MySQL支持DML(数据操作语言)、DDL(数据定义语言)、存储过程、视图、触发器、自定 义函数等多种SQL语言接口
-
Parser: 解析器
- 在解析器中对 SQL 语句进行语法分析、语义分析。将SQL语句分解成数据结构,并将这个结构 传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错 误,那么就说明这个SQL语句是不合理的。
- 在SQL命令传递到解析器的时候会被解析器验证和解析,并为其创建 语法树 ,并根据数据字 典丰富查询语法树,会 验证该客户端是否具有执行该查询的权限 。创建好语法树后,MySQL还 会对SQl查询进行语法上的优化,进行查询重写。
-
Optimizer: 查询优化器
- SQL语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个 执行计划 。
- 这个执行计划表明应该 使用哪些索引 进行查询(全表检索还是使用索引检索),表之间的连 接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将 查询结果返回给用户。
- 它使用“ 选取-投影-连接 ”策略进行查询。例如:
SELECT id,name FROM student WHERE gender = '女';
这个SELECT查询先根据WHERE语句进行 选取 ,而不是将表全部查询出来以后再进行gender过 滤。 这个SELECT查询先根据id和name进行属性 投影 ,而不是将属性全部取出以后再进行过 滤,将这两个查询条件 连接 起来生成最终查询结果。
-
Caches & Buffers: 查询缓存组件
- MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结 果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过 程了,直接将结果反馈给客户端。
- 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等 。 这个查询缓存可以在 不同客户端之间共享 。
- 从MySQL 5.7.20开始,不推荐使用查询缓存,并在 MySQL 8.0中删除 。
1.5 第三层:引擎层
插件式存储引擎层( Storage Engines),真正的负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样 我们可以根据自己的实际需要进行选取。
MySQL 8.0.25默认支持的存储引擎如下:
1.6 存储层
所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在文件系统 上,以文件
的方式存在的,并完成与存储引擎的交互。当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用 DAS、NAS、SAN等各种存储系统。
1.7 小结
MySQL架构图本节开篇所示。下面为了熟悉SQL执行流程方便,我们可以简化如下:
简化为三层结构:
- 连接层:客户端和服务器端建立连接,客户端发送 SQL 至服务器端;
- SQL 层(服务层):对 SQL 语句进行查询处理;与数据库文件的存储方式无关;
- 存储引擎层:与数据库文件打交道,负责数据的存储和读取。
2. SQL执行流程
2.1 MySQL中的SQL执行流程
MySQL的查询流程:
- 查询缓存:Server 如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端;如果没 有,就进入到解析器阶段。需要说明的是,因为查询缓存往往效率不高,所以在 MySQL8.0 之后就抛弃了这个功能。
总之,因为查询缓存往往弊大于利,查询缓存的失效非常频繁。
一般建议大家在静态表里使用查询缓存,什么叫静态表
呢?就是一般我们极少更新的表。比如,一个系统配置表、字典表,这张表上的查询才适合使用查询缓存。好在MySQL也提供了这种“按需使用
”的方式。你可以将 my.cnf 参数 query_cache_type 设置成 DEMAND,代表当 sql 语句中有 SQL_CACHE关键字时才缓存。比如:
# query_cache_type 有3个值。 0代表关闭查询缓存OFF,1代表开启ON,2代表(DEMAND)
query_cache_type=2
这样对于默认的SQL语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以供SQL_CACHE显示指定,像下面这个语句一样:
SELECT SQl_CACHE * FROM test WHERE ID=5;
查看当前 mysql 实例是否开启缓存机制
# MySQL5.7中:
show global variables like "%query_cache_type%";
监控查询缓存的命中率:
show status like '%Qcache%';
运行结果解析:
Qcache_free_blocks
: 表示查询缓存中海油多少剩余的blocks,如果该值显示较大,则说明查询缓存中的内部碎片
过多了,可能在一定的时间进行整理。
Qcache_free_memory
: 查询缓存的内存大小,通过这个参数可以很清晰的知道当前系统的查询内存是否够用,DBA可以根据实际情况做出调整。
Qcache_hits
: 表示有 多少次命中缓存
。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大,缓存效果越理想。
Qcache_inserts
: 表示多少次未命中然后插入
,意思是新来的SQL请求在缓存中未找到,不得不执行查询处理,执行查询处理后把结果insert到查询缓存中。这样的情况的次数越多,表示查询缓存应用到的比较少,效果也就不理想。当然系统刚启动后,查询缓存是空的,这也正常。
Qcache_lowmem_prunes
: 该参数记录有多少条查询因为内存不足而被移除
出查询缓存。通过这个值,用户可以适当的调整缓存大小。
Qcache_not_cached
: 表示因为query_cache_type的设置而没有被缓存的查询数量。
Qcache_queries_in_cache
: 当前缓存中缓存的查询数量
。
Qcache_total_blocks
: 当前缓存的block数量。
- 解析器:在解析器中对 SQL 语句进行语法分析、语义分析。
如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL需要知道你要做什么,因此需要对SQL语句做解析。SQL语句的分析分为词法分析与语法分析。
分析器先做“ 词法分析
”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面 的字符串分别是什么,代表什么。
MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语 句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
接着,要做“ 语法分析
”。根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输 入的这个 SQL 语句是否 满足 MySQL 语法
。
select department_id,job_id, avg(salary) from employees group by department_id;
如果SQL语句正确,则会生成一个这样的语法树:
下图是SQL分词分析的过程步骤:
至此解析器的工作任务也基本圆满了。
-
优化器:在优化器中会确定 SQL 语句的执行路径,比如是根据
全表检索
,还是根据索引检索
等。经过解释器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
比如:优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联 (join) 的时候,决定各个表的连接顺序,还有表达式简化、子查询转为连接、外连接转为内连接等。
举例:如下语句是执行两个表的 join:
select * from test1 join test2 using(ID)
where test1.name='zhangwei' and test2.name='mysql高级课程';
方案1:可以先从表 test1 里面取出 name='zhangwei'的记录的 ID 值,再根据 ID 值关联到表 test2,再判
断 test2 里面 name的值是否等于 'mysql高级课程'。
方案2:可以先从表 test2 里面取出 name='mysql高级课程' 的记录的 ID 值,再根据 ID 值关联到 test1,
再判断 test1 里面 name的值是否等于 zhangwei。
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化
器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等。后面讲到索引我们再谈。
在查询优化器中,可以分为 逻辑查询
优化阶段和 物理查询
优化阶段。
逻辑查询优化就是通过改变SQL语句的内容来使得SQL查询更高效,同时为物理查询优化提供更多的候选执行计划。通常采用的方式是对SQL语句进行等价变换
,对查询进行重写
,而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语义进行了外连接消除、嵌套连接消除等。
物理查询优化是基于关系代数进行的查询重写,而关系代数的每一步都对应着物理计算,这些物理计算往往存在多种算法,因此需要计算各种物理路径的代价,从中选择代价最小的作为执行计划。在这个阶段里,对于单表和多表连接的操作,需要高效地使用索引
,提升查询效率。
- 执行器:
截止到现在,还没有真正去读写真实的表,仅仅只是产出了一个执行计划。于是就进入了执行器阶段 。
在执行之前需要判断该用户是否 具备权限
。如果没有,就会返回权限错误。如果具备权限,就执行 SQL 查询并返回结果。在 MySQL8.0 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。
select * from test where id=1;
比如:表 test 中,ID 字段没有索引,那么执行器的执行流程是这样的:
调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是1,如果不是则跳过,如果是则将这行存在结果集中;
调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。对于有索引的表,执行的逻辑也差不多。
SQL 语句在 MySQL 中的流程是: SQL语句
→查询缓存
→解析器
→优化器
→执行器
。
2.2 MySQL8中SQL执行原理
1) 确认profiling是否开启
了解查询语句底层执行的过程:select @profiling
或者 show variables like '%profiling'
查看是否开启计划。开启它可以让MySQL收集在SQL
执行时所使用的资源情况,命令如下:
mysql> select @@profiling;
mysql> show variables like 'profiling';
profiling=0 代表关闭,我们需要把 profiling 打开,即设置为 1:
mysql> set profiling=1;
2) 多次执行相同SQL查询
然后我们执行一个 SQL 查询(你可以执行任何一个 SQL 查询):
mysql> select * from employees;
3) 查看profiles
查看当前会话所产生的所有 profiles:
mysql> show profiles; # 显示最近的几次查询
4) 查看profile
显示执行计划,查看程序的执行步骤:
mysql> show profile;
当然你也可以查询指定的 Query ID,比如:
mysql> show profile for query 7;
查询 SQL 的执行时间结果和上面是一样的。
此外,还可以查询更丰富的内容:
mysql> show profile cpu,block io for query 6;
继续:
mysql> show profile cpu,block io for query 7;
1、除了查看cpu、io阻塞等参数情况,还可以查询下列参数的利用情况。
Syntax:
SHOW PROFILE [type [, type] ... ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type:
| ALL -- 显示所有参数的开销信息
| BLOCK IO -- 显示IO的相关开销
| CONTEXT SWITCHES -- 上下文切换相关开销
| CPU -- 显示CPU相关开销信息
| IPC -- 显示发送和接收相关开销信息
| MEMORY -- 显示内存相关开销信息
| PAGE FAULTS -- 显示页面错误相关开销信息
| SOURCE -- 显示和Source_function,Source_file,Source_line 相关的开销信息
| SWAPS -- 显示交换次数相关的开销信息
2、发现两次查询当前情况都一致,说明没有缓存。
在 8.0 版本之后,MySQL 不再支持缓存的查询
。一旦数据表有更新,缓存都将清空,因此只有数据表是静态的时候,或者数据表很少发生变化时,使用缓存查询才有价值,否则如果数据表经常更新,反而增加了 SQL 的查询时间。
2.3 MySQL5.7中SQL执行原理
上述操作在MySQL5.7中测试,发现前后两次相同的sql语句,执行的查询过程仍然是相同的。不是会使用 缓存吗?这里我们需要 显式开启查询缓存模式 。在MySQL5.7中如下设置:
1) 配置文件中开启查询缓存
在 /etc/my.cnf 中新增一行:
query_cache_type=1
2) 重启mysql服务
systemctl restart mysqld
3) 开启查询执行计划
由于重启过服务,需要重新执行如下指令,开启profiling。
mysql> set profiling=1;
4) 执行语句两次:
mysql> select * from locations;
5) 查看profiles
6) 查看profile
显示执行计划,查看程序的执行步骤:
mysql> show profile for query 1;
mysql> show profile for query 2;
结论不言而喻。执行编号2时,比执行编号1时少了很多信息,从截图中可以看出查询语句直接从缓存中 获取数据。
2.4 SQL语法顺序
随着Mysql版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同 而动态调整执行顺序。
3. 数据库缓冲池(buffer pool)
InnoDB
存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请占用内存来作为数据缓冲池
,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问。
这样做的好处是可以让磁盘活动最小化,从而 减少与磁盘直接进行 I/O 的时间
。要知道,这种策略对提升 SQL 语句的查询性能来说至关重要。如果索引的数据在缓冲池里,那么访问的成本就会降低很多。
3.1 缓冲池 vs 查询缓存
缓冲池和查询缓存是一个东西吗?不是。
1) 缓冲池(Buffer Pool)
首先我们需要了解在 InnoDB 存储引擎中,缓冲池都包括了哪些。
在 InnoDB 存储引擎中有一部分数据会放到内存中,缓冲池则占了这部分内存的大部分,它用来存储各种数据的缓存,如下图所示:
从图中,你能看到 InnoDB 缓冲池包括了数据页、索引页、插入缓冲、锁信息、自适应 Hash 和数据字典信息等。
缓存池的重要性:
缓存原则:
“ 位置 * 频次
”这个原则,可以帮我们对 I/O 访问效率进行优化。
首先,位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。
其次,频次决定优先级顺序。因为缓冲池的大小是有限的,比如磁盘有 200G,但是内存只有 16G,缓冲池大小只有 1G,就无法将所有数据都加载到缓冲池里,这时就涉及到优先级顺序,会优先对使用频次高的热数据进行加载
。
缓冲池的预读特性:
缓冲池的作用就是提升 I/O 效率,而我们进行读取数据的时候存在一个“局部性原理”,也就是说我们使用了一些数据,大概率还会使用它周围的一些数据,因此采用“预读”的机制提前加载,可以减少未来可能的磁盘 I/O 操作。
2) 查询缓存
那么什么是查询缓存呢?
查询缓存是提前把 查询结果缓存起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在 MySQL 中的查询缓存,不是缓存查询计划,而是查询对应的结果。因为命中条件苛刻,而且只要数据表 发生变化,查询缓存就会失效,因此命中率低。
3.2 缓冲池如何读取数据
缓冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面 是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进行读取。
缓存在数据库中的结构和作用如下图所示:
如果我们执行 SQL 语句的时候更新了缓存池中的数据,那么这些数据会马上同步到磁盘上吗?
实际上,当我们对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新
到磁盘中。注意并不是每次发生更新操作,都会立即进行磁盘回写。缓冲池会采用一种叫做 checkpoint 的机制
将数据回写到磁盘上,这样做的好处就是提升了数据库的整体性能。
比如,当缓冲池不够用
时,需要释放掉一些不常用的页,此时就可以强行采用checkpoint的方式,将不常用的脏页回写到磁盘上,然后再从缓存池中将这些页释放掉。这里的脏页 (dirty page) 指的是缓冲池中被修改过的页,与磁盘上的数据页不一致。
3.3 查看/设置缓冲池的大小
如果你使用的是 MySQL MyISAM 存储引擎,它只缓存索引,不缓存数据,对应的键缓存参数为key_buffer_size
,你可以用它进行查看。
如果你使用的是 InnoDB 存储引擎,可以通过查看 innodb_buffer_pool_size 变量来查看缓冲池的大小。命令如下:
show variables like 'innodb_buffer_pool_size';
你能看到此时 InnoDB 的缓冲池大小只有 134217728/1024/1024=128MB。我们可以修改缓冲池大小,比如改为256MB,方法如下:
set global innodb_buffer_pool_size = 268435456;
或者:
[server]
innodb_buffer_pool_size = 268435456
3.4 多个Buffer Pool实例
[server]
innodb_buffer_pool_instances = 2
这样就表明我们要创建2个 Buffer Pool
实例。
我们看下如何查看缓冲池的个数,使用命令:
show variables like 'innodb_buffer_pool_instances';
那每个 Buffer Pool 实例实际占多少内存空间呢?其实使用这个公式算出来的:
innodb_buffer_pool_size/innodb_buffer_pool_instances
也就是总共的大小除以实例的个数,结果就是每个 Buffer Pool 实例占用的大小。
不过也不是说 Buffer Pool 实例创建的越多越好,分别管理各个 Buffer Pool 也是需要性能开销的,InnDB规定:当innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的,InnoDB会默认把innodb_buffer_pool_instances的值修改为1。而我们鼓励在 Buffer Pool 大于等于 1G 的时候设置多个 Buffer Pool 实例。
3.5 引申问题
Buffer Pool是MySQL内存结构中十分核心的一个组成,你可以先把它想象成一个黑盒子。
黑盒下的更新数据流程
当我们查询数据的时候,会先去 Buffer Pool 中查询。如果 Buffer Pool 中不存在,存储引擎会先将数据从磁盘加载到 Buffer Pool 中,然后将数据返回给客户端;同理,当我们更新某个数据的时候,如果这个数据不存在于 Buffer Pool,同样会先数据加载进来,然后修改内存的数据。被修改的数据会在之后统一刷入磁盘。
我更新到一半突然发生错误了,想要回滚到更新之前的版本,该怎么办?连数据持久化的保证、事务回滚都做不到还谈什么崩溃恢复?
答案:Redo Log & Undo Log
大数据技术之_29_MySQL 高級面试重点串讲_02_Mysql 简介+Linux 版的安装+逻辑架构介绍+性能优化+性能分析+查询截取分析+分区分库分表简介+锁机制+主从复制
第1章 Mysql 简介1.1 概述1.2 高级 MySQL第2章 Mysql Linux 版的安装2.1 下载地址2.2 检查当前系统是否安装过 mysql2.3 修改 Mysql 配置文件位置2.4 修改字符集和数据存储路径2.5 MySQL 的安装位置说明2.6 Mysql 配置文件说明2.7 Mysql 的数据存放目录第3章 Mysql 逻辑架构介绍3.1 总体概览3.2 查询说明第4章 Mysql 性能优化4.1 影响 mysql 的性能因素4.2 查询与索引优化分析4.2.1 性能下降SQL慢的原因4.2.2 常见通用的 Join 查询4.3 mysql 索引结构与检索原理4.4 哪些情况需要创建索引4.5 哪些情况不要创建索引第5章 性能分析5.1 索引优化5.1.1 索引分析5.1.2 索引失效(应该避免)5.1.3 一般性建议第6章 查询截取分析6.1 查询优化6.1.1 永远小表驱动大表6.1.2 order by 关键字优化6.1.3 gruop by 关键字优化6.2 慢查询日志6.3 批量数据脚本(数据库编程)6.4 show profile6.5 全局查询日志第7章 大数据量处理理论第8章 Mysql 分区分库分表简介8.1 Mysql 分区8.2 List 分区8.3 对 NULL 值的处理第9章 MySql 锁机制9.1 概述9.2 三锁9.2.1 表锁(偏读)案例演示9.2.2 行锁(偏写)案例演示第10章 MySql 主从复制10.1 复制的基本原理10.2 复制的基本原则 和 复制的最大问题10.3 一主一从常见配置
先完成再完美,先功能再性能。
大胆假设,小心求证。
第1章 Mysql 简介
1.1 概述
MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。
MySQL 是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL 是开源的,所以你不需要支付额外的费用。
MySQL 支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
MySQL 使用标准的 SQL 数据语言形式。
MySQL 可以允许于多个系统上,并且支持多种语言。这些编程语言包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby 和 Tcl 等。
MySQL 对 PHP 有很好的支持,PHP 是目前最流行的 Web 开发语言。
MySQL 支持大型数据库,支持 5000 万条记录的数据仓库,32 位系统表文件最大可支持 4GB,64 位系统支持最大的表文件为 8TB。
MySQL 是可以定制的,采用了 GPL 协议,你可以修改源码来开发自己的 Mysql 系统。
1.2 高级 MySQL
完整的 mysql 优化需要很深的功底,大公司甚至有专门的 DBA 写下述内容:
第2章 Mysql Linux 版的安装
2.1 下载地址
官网下载地址:http://dev.mysql.com/downloads/mysql/
选择对应的 MySQL 版本、操作系统 和 位数,点击 Download(本博文中以版本 5.5.48 为例)
下载地址:
https://downloads.mysql.com/archives/get/file/MySQL-server-5.5.48-1.linux2.6.x86_64.rpm
https://downloads.mysql.com/archives/get/file/MySQL-client-5.5.48-1.linux2.6.x86_64.rpm
2.2 检查当前系统是否安装过 mysql
第一步:以 root 身份登录,查看 mysql 是否安装。
rpm -qa | grep mysql
第二步:以 root 身份登录,如果 mysql 的版本不是想要的版本。需要把原来的 mysql 卸载。
使用 yum 命令卸载
yum remove mysql mysql-server mysql-libs mysql-common
使用 rpm 命令卸载
rpm -e RPM软件包名(该名字是第一步命令查出来的名字)
rm -rf /var/lib/mysql
rm -f /etc/my.cnf
注意
:使用 yum 命令卸载,因为 yum 命令可以自动删除与 mysql 相关的依赖;如果使用 rpm 命令卸载,则还需要手动去删除和mysql相关的文件。
第三步1:安装 mysql。需要使用 yum 命令安装。在安装 mysql 之前需要安装 mysql 的下载源。需要从 oracle 的官方网站下载。
1)下载 mysql 的源包:
我们是 centos6.8 对应的 rpm 包为:mysql-community-release-el6-5.noarch.rpm
命令:wget http://repo.mysql.com/mysql-community-release-el6-5.noarch.rpm
2)安装 mysql 下载源:
yum localinstall mysql-community-release-el6-5.noarch.rpm
3)在线安装社区版的 mysql:
yum install mysql-community-server
或者在线安装收费版本的 mysql:
yum install mysql-server
两种方式均可,建议安装社区版。
第三步2:我们也可以使用 rpm 命令进行安装。
安装 mysql 服务端(注意提示)
安装 mysql 客户端
查看 MySQL 安装时创建的 mysql 用户和 mysql 组
或者可以执行
mysqladmin --version
命令。类似 java -version
,如果打出消息,即为安装成功。
mysql 服务的启+停
设置开机自启动 mysql 服务
查看 设置开机自启动 mysql 服务
第四步:启动 mysql。
service mysql start
注意:
第五步:需要给 root 用户设置密码。
/usr/bin/mysqladmin -u root password ‘new-password‘ #为 root 账号设置密码
第六步:登录 mysql。
[[email protected] ~]# mysql -uroot -p
第七步:需要先登录到 mysql,进行远程连接授权。
GRANT ALL PRIVILEGES ON *.* TO ‘myuser‘@‘%‘ IDENTIFIED BY ‘mypassword‘ WITH GRANT OPTION;
注意
:‘myuser‘、‘mypassword‘ 需要替换成实际的用户名和密码。
2.3 修改 Mysql 配置文件位置
Mysql 5.5 版本:cp /usr/share/mysql/my-huge.cnf /etc/my.cnf
Mysql 5.6 版本:cp /usr/share/mysql/my-default.cnf /etc/my.cnf
2.4 修改字符集和数据存储路径
1、查看字符集
mysql> show variables like ‘character%‘; 或者
mysql> show variables like ‘%char%‘;
默认的 数据库 和 服务器都用了 latin1,所以会乱码。
2、修改
下图中的红色字体部分表示要修改的地方
my.cnf 中要修改的代码如下:
[client]
#password = your_password
port = 3306
socket = /var/lib/mysql/mysql.sock
default-character-set=utf8
# The MySQL server
[mysqld]
port = 3306
character_set_server=utf8
character_set_client=utf8
collation-server=utf8_general_ci
socket = /var/lib/mysql/mysql.sock
skip-external-locking
key_buffer_size = 384M
max_allowed_packet = 1M
table_open_cache = 512
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size = 32M
# Try number of CPU‘s*2 for thread_concurrency
thread_concurrency = 8
lower_case_table_names=1
max_connections=1000
[mysql]
no-auto-rehash
default-character-set=utf8
3、重启 mysql
service mysql stop
service mysql start
4、重新连接后重新 create databse 并使用新建库,然后再重新建表试试。
5、还是乱码的话就设值 init_connect=‘SET NAMES utf8‘ ##设定连接 mysql 是 UTF8 编码
。
2.5 MySQL 的安装位置说明
在 linux 下查看安装目录,命令:ps -ef | grep mysql
2.6 Mysql 配置文件说明
2.7 Mysql 的数据存放目录
/var/lib/mysql
第3章 Mysql 逻辑架构介绍
3.1 总体概览
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构
上,插件式的存储引擎架构
将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
3.2 查询说明
首先,mysql 的查询流程大致是:mysql 客户端通过协议与 mysql 服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析。
有一系列预处理,比如检查语句是否写正确了,然后是查询优化(比如是否使用索引扫描,如果是一个不可能的条件,则提前终止),生成查询计划,然后查询引擎启动,开始执行查询,从底层存储引擎调用 API 获取数据,最后返回给客户端。怎么存数据、怎么取数据,都与存储引擎有关。
然后,mysql 默认使用的 BTREE 索引,并且一个大方向是,无论怎么折腾 sql,至少在目前来说,mysql 最多只用到表中的一个索引。
第4章 Mysql 性能优化
4.1 影响 mysql 的性能因素
4.2 查询与索引优化分析
4.2.1 性能下降SQL慢的原因
4.2.2 常见通用的 Join 查询
详解见链接:https://www.cnblogs.com/chenmingjun/p/11012827.html
4.3 mysql 索引结构与检索原理
mysql 索引结构
检索原理
【初始化介绍】
一颗 b+ 树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3,P1 表示小于 17 的磁盘块,P2 表示在 17 和 35 之间的磁盘块,P3 表示大于 35 的磁盘块。
真实的数据存在于叶子节点即 3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如 17、35 并不真实存在于数据表中。
【查找过程】
如果要查找数据项 29,那么首先会把磁盘块 1 由磁盘加载到内存,此时发生一次 IO,在内存中用二分查找确定 29 在 17 和 35 之间,锁定磁盘块 1 的 P2 指针,内存时间因为非常短(相比磁盘的 IO)可以忽略不计,通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存,发生第二次 IO,29 在 26 和 30 之间,锁定磁盘块 3 的 P2 指针,通过指针加载磁盘块 8 到内存,发生第三次 IO,同时内存中做二分查找找到 29,结束查询,总计三次 IO。
真实的情况是,3 层的 b+ 树可以表示上百万的数据,如果上百万的数据查找只需要三次 IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次 IO,那么总共需要百万次的 IO,显然成本非常非常高。
4.4 哪些情况需要创建索引
4.5 哪些情况不要创建索引
MySql Query Optimizer
(MySQL 查询优化器)
1、Mysql 中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(MySQL 认为最优的数据检索方式,但不见得是 DBA 认为是最优的,这部分最耗费时间)
2、当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimizer 时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
第5章 性能分析
5.1 索引优化
5.1.1 索引分析
1、单表
建表 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‘),
(1, 1, 3, 3, ‘3‘, ‘3‘);
SELECT * FROM article;
案例
# 查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id。
# 1.0 第 1 次EXPLAIN
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
| 1 | SIMPLE | article | ALL | NULL | NULL | NULL | NULL | 3 | Using where; Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
#结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
# 开始优化:
# 1.1 新建索引+删除索引
#ALTER TABLE `article` ADD INDEX idx_article_ccv (`category_id`, `comments`, `views`);
CREATE INDEX idx_article_ccv ON article (category_id, comments, views);
DROP INDEX idx_article_ccv ON article;
# 1.2 第 2 次EXPLAIN
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| 1 | SIMPLE | article | range | idx_article_ccv | idx_article_ccv | 8 | NULL | 1 | Using where; Using filesort |
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments = 3 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
| 1 | SIMPLE | article | ref | idx_article_ccv | idx_article_ccv | 8 | const,const | 1 | Using where |
+----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
# 结论:
# type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。
# 但是我们已经建立了索引,为啥没用呢?
# 这是因为按照 BTree 索引的工作原理:(分组之前必排序)
# 先排序 category_id,
# 如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。
# 当 comments 字段在联合索引里处于中间位置时,
# 因 comments > 1 条件是一个范围值(所谓 range),
# MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。
# 1.3 删除第 1 次建立的索引
DROP INDEX idx_article_ccv ON article;
# 1.4 第 2 次新建索引
#ALTER TABLE `article` ADD INDEX idx_article_cv (`category_id`, `views`);
CREATE INDEX idx_article_cv ON article (category_id, views);
# 1.5 第 3 次 EXPLAIN
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------+----------------+----------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+----------------+----------------+---------+-------+------+-------------+
| 1 | SIMPLE | article | ref | idx_article_cv | idx_article_cv | 4 | const | 2 | Using where |
+----+-------------+---------+------+----------------+----------------+---------+-------+------+-------------+
# 结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
# 删除第 2 次建立的索引
DROP INDEX idx_article_cv ON article;
2、两表
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`)
);
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
select * from class;
select * from book;
案例
# 下面开始第 2 次 explain 分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ALL | NULL | NULL | NULL | NULL | 20 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
# 结论:type 有 All
# 添加索引优化
ALTER TABLE `book` ADD INDEX Y (`card`);
# 第 2 次 explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ref | Y | Y | 4 | db2019.class.card | 1 | Using index |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
# 可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。
# 这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左表一定都有,
# 所以右表是我们的关键点,一定需要建立索引。
# 删除旧索引 + 新建新索引 + 第 3 次 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;
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | class | index | NULL | X | 4 | NULL | 20 | Using index |
| 1 | SIMPLE | book | ALL | NULL | NULL | NULL | NULL | 20 | |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
# 然后来看一个右连接查询(同理):
# 优化较明显。这是因为 RIGHT JOIN 条件用于确定如何从左表搜索行,右表一定都有,
# 所以左表是我们的关键点,一定需要建立索引。
EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
DROP INDEX X ON class;
ALTER TABLE `book` ADD INDEX Y (`card`);
# 右连接,基本无变化
EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
3、三表
在上面二表的基础上再加一张表
CREATE TABLE IF NOT EXISTS `phone` (
`phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`phoneid`)
) ENGINE = INNODB;
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
案例
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | phone | ALL | NULL | NULL | NULL | NULL | 20 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
以左连接为例:(将右边对应的字段建立索引)
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;
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ref | Y | Y | 4 | db2019.class.card | 1 | Using index |
| 1 | SIMPLE | phone | ref | z | z | 4 | db2019.book.card | 1 | Using index |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
# 后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
===============================================================================================
【结论】
join 语句的优化:
1、尽可能减少 join 语句中的 Nested Loop(嵌套循环) 的循环总次数;“永远用小结果集驱动大的结果集”(小表驱动大表);
2、优先优化 Nested Loop(嵌套循环) 的内层循环;
3、保证 join 语句中的被驱动表上 join 条件字段已经被索引;
4、当无法保证被驱动表的 join 条件字段被索引且内存资源充足的前提下,不要太吝惜 join buffer 的设置(即将该值调大些);
5.1.2 索引失效(应该避免)
详解见链接:https://www.cnblogs.com/chenmingjun/p/11012827.html
面试题讲解:
我们创建了复合索引 idx_test03_c1234,根据以下 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)
);
insert into test03 (c1,c2,c3,c4,c5) values (‘a1‘,‘a2‘,‘a3‘,‘a4‘,‘a5‘);
insert into test03 (c1,c2,c3,c4,c5) values (‘b1‘,‘b2‘,‘b3‘,‘b4‘,‘b5‘);
insert into test03 (c1,c2,c3,c4,c5) values (‘c1‘,‘c2‘,‘c3‘,‘c4‘,‘c5‘);
insert into test03 (c1,c2,c3,c4,c5) values (‘d1‘,‘d2‘,‘d3‘,‘d4‘,‘d5‘);
insert into test03 (c1,c2,c3,c4,c5) values (‘e1‘,‘e2‘,‘e3‘,‘e4‘,‘e5‘);
select * from test03;
【建索引】
create index idx_test03_c1234 on test03(c1, c2, c3, c4);
show index from test03;
问题:我们创建了复合索引 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‘;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 93 | const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
===================================================================================================================================
1) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c3=‘a3‘ and c4=‘a4‘; # 索引都使用到了,因为 MySQL 底层进行了优化
2) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c4=‘a4‘ and c3=‘a3‘; # 索引都使用到了,因为 MySQL 底层进行了优化
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
结论:索引的使用顺序与索引的创建顺序无关,但是最好按照创建索引的顺序使用索引。这样可以避免一次的 sql 自身的查询优化。
3) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c3>‘a3‘ and c4=‘a4‘; # 使用到了 c1、c2、c3索引,c3 范围之后全失效
4) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c4>‘a4‘ and c3=‘a3‘; # 索引都使用到了,因为 MySQL 底层进行了优化,c4 范围之后全失效,但是 c4 之后没有范围了
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| 1 | SIMPLE | test03 | range | idx_test03_c1234 | idx_test03_c1234 | 93 | NULL | 1 | Using where |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| 1 | SIMPLE | test03 | range | idx_test03_c1234 | idx_test03_c1234 | 124 | NULL | 1 | Using where |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
5) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c4=‘a4‘ order by c3; # 索引 c3 作用在排序而不是查找(只是用到了一半,但是不算)
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
6) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ order by c3; # 使用到了 c1、c2 索引
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
7) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ order by c4; # 使用到了 c1、c2 索引,出现了 filesort,性能下降
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-----------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-----------------------------+
8)
8.1 explain select * from test03 where c1=‘a1‘ and c5=‘a5‘ order by c2, c3;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
只用到了 c1 一个字段索引,但是 c2、c3 用于排序,无 filesort。
8.2 explain select * from test03 where c1=‘a1‘ and c5=‘a5‘ order by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
只用到了 c1 一个字段索引,且出现了 filesort,我们建的索引是 1234,它没有按照顺序来,3 2 颠倒了。
9) explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ order by c2, c3;
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
用到了 c1、c2 两个字段索引,但是 c2、c3 用于排序,无 filesort。
10)
explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c5=‘a5‘ order by c2, c3;
结果同 (9),用到了 c1、c2 两个字段索引,但是 c2、c3 用于排序,无 filesort。
explain select * from test03 where c1=‘a1‘ and c2=‘a2‘ and c5=‘a5‘ order by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
本例有常量 c2 的情况,和 8.2 进行对比,无 filesort。
小结:一般来说,order by 的字段顺序没有跟创建索引的字段顺序一致时,一般会出现 filesort,但是排序字段有常量的情况时,那么 order by 的字段顺序没有跟创建索引的字段顺序一致时 则不会出现 filesort,案例如上。
explain select * from test03 where c1=‘a1‘ and c5=‘a5‘ order by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
只用到了 c1 一个字段索引,且出现了 filesort。
11)explain select * from test03 where c1=‘a1‘ and c4=‘a4‘ group by c2, c3;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
只用到了 c1 一个字段索引,但是 c2、c3 用于排序,无 filesort。
12)explain select * from test03 where c1=‘a1‘ and c4=‘a4‘ group by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+----------------------------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where; Using temporary; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+----------------------------------------------+
只用到了 c1 一个字段索引,且出现了 temporary 和 filesort。因为分组之前必排序(内部排序)。
小结:
1、定值、范围还是排序,一般 order by 是给个范围。
2、group by 基本上都需要进行内部排序,如果 group by 字段顺序错了之后,内部会有临时表产生。
5.1.3 一般性建议
第6章 查询截取分析
面试问题:你对 Mysql 的优化有哪些?
答:
1、代码至少跑一天,观察慢 sql 的情况。
2、开启慢查询日志,设置阈值,比如超过 5 秒钟的慢 sql,并将它捕获出来。
3、explain + 慢 sql 分析。
4、show profile,查看 sql z在服务器里面的执行细节和生命周期情况。
5、运维经理或者 DBA 进行 sql 数据库服务器的参数调优。
6.1 查询优化
6.1.1 永远小表驱动大表
类似嵌套循环(Nested Loop)
6.1.2 order by 关键字优化
1、ORDER BY 子句,尽量使用 index 方式排序,避免使用 filesort 方式排序。
建表 SQL
CREATE TABLE tblA (
#id int primary key not null auto_increment,
age INT,
birth TIMESTAMP NOT NULL
);
INSERT INTO tblA (age,birth) VALUES (22, NOW());
INSERT INTO tblA (age,birth) VALUES (23, NOW());
INSERT INTO tblA (age,birth) VALUES (24, NOW());
CREATE INDEX idx_A_ageBirth ON tblA(age, birth);
SELECT * FROM tblA;
案例1
mysql> explain select * from tblA where age > 20 order by age;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where age > 20 order by age, birth;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where age > 20 order by birth;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where age > 20 order by birth, age;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
1 row in set (0.00 sec)
mysql>
截图如下:
案例2
mysql> explain select * from tblA order by birth;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where birth > ‘2019-06-12 00:00:00‘ order by birth;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+------------------------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where birth > ‘2019-06-12 00:00:00‘ order by age;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA order by age asc, birth desc;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)
mysql>
截图如下:
结论:
1、MySQL 支持二种方式的排序:filesort 和 index,index 效率高。
2、它指 MySQL 扫描索引本身完成排序。filesort 方式效率较低。
ORDER BY 满足两种情况时,会使用 index方式排序:
1、ORDER BY 语句使用索引最左前列。
2、使用 where 子句与 order by 子句条件列组合满足索引最左前列。
2、尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀原则。
3、如果不在索引列上,filesort 有两种算法:mysql 就要启动双路排序和单路排序。
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 活动和低的处理器使用率。
小结
6.1.3 gruop by 关键字优化
6.2 慢查询日志
是什么
怎么玩
说明
查看是否开启和如何开启
使用 set global slow_query_log=1; 开启了慢查询日志只对当前数据生效,如果 MySQL 重启后则会失效。
如果要永久生效,就必须修改配置文件 my.cnf(其他系统变量亦是如此)
那么开启了慢查询日志后,什么样的 sql 才会记录到慢查询日志里面呢?
案例
日志分析工具 mysqldumpslow
在生产环境中,如果手工分析日志、查找、分析SQL,显然是个体力活,MySQL 提供了日志分析工具 mysqldumpslow。
参数详解及示例
6.3 批量数据脚本(数据库编程)
详解见链接:https://www.cnblogs.com/chenmingjun/p/11012827.html
6.4 show profile
6.5 全局查询日志
这招只允许在测试环境下使用,永远不允许在生产环境下用。
配置启动:
编码启用:
第7章 大数据量处理理论
第8章 Mysql 分区分库分表简介
8.1 Mysql 分区
Mysql 分区是什么?(PARTITION)
如果一张表的数据量太大的话,那么 myd,myi 就会变的很大,查找数据就会变的很慢,这个时候我们可以利用 mysql 的分区功能,在物理上将这一张表对应的三个文件,分割成许多个小块,这样呢,我们查找一条数据时,就不用全部查找了,只要知道这条数据在哪一块,然后在那一块找就行了。如果表的数据太大,可能一个磁盘放不下,这个时候,我们可以把数据分配到不同的磁盘里面去。
Range 分区案例
建表 SQL
CREATE TABLE tbl_new (
id INT NOT NULL PRIMARY KEY,
title VARCHAR(20) NOT NULL DEFAULT ‘‘
) ENGINE MYISAM CHARSET utf8
PARTITION BY RANGE(id) (
PARTITION t0 VALUES LESS THAN(10),
PARTITION t1 VALUES LESS THAN(20),
PARTITION t2 VALUES LESS THAN(MAXVALUE)
);
INSERT INTO tbl_new VALUES (1,‘z3‘);
INSERT INTO tbl_new VALUES (2,‘z4‘);
INSERT INTO tbl_new VALUES (3,‘z5‘);
INSERT INTO tbl_new VALUES (4,‘z6‘);
查看分区
插入数据后再次查看分区
8.2 List 分区
原理:
MySQL 中的 LIST 分区在很多方面类似于 RANGE 分区。和按照 RANGE 分区一样,每个分区必须明确定义。它们的主要区别在于,LIST 分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而 RANGE 分区是从属于一个连续区间值的集合。
List 分区案例
建表 SQL
create table area (
id INT NOT NULL PRIMARY KEY,
region varchar(20)
)engine myisam charset utf8;
insert into area values (1,‘bj‘);
insert into area values (2,‘sh‘);
insert into area values (3,‘gz‘);
insert into area values (4,‘sz‘);
create table user (
uid int not null,
userName varchar(20),
area_id int
)engine myisam charset utf8
partition by list(area_id) (
partition bj values in (1),
partition sh values in (2),
partition gz values in (3),
partition sz values in (4)
);
insert into user (uid, userName, area_id) values (1,‘z3‘,1);
insert into user (uid, userName, area_id) values (2,‘z4‘,2);
insert into user (uid, userName, area_id) values (3,‘z5‘,3);
查看分区
8.3 对 NULL 值的处理
MySQL 中的分区在禁止空值 NULL 上没有进行处理,无论它是一个列值还是一个用户定义表达式的值,一般而言,在这种情况下 MySQL 把 NULL 当做零。如果你不希望出现类似情况,建议在设计表时声明该列 “NOT NULL”。
第9章 MySql 锁机制
9.1 概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(如 CPU、RAM、I/O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
锁的分类
9.2 三锁
9.2.1 表锁(偏读)案例演示
建表 SQL
【表级锁分析--建表 SQL】
create table mylock (
id int not null primary key auto_increment,
name varchar(20)
)engine myisam;
insert into mylock (name) values(‘a‘);
insert into mylock (name) values(‘b‘);
insert into mylock (name) values(‘c‘);
insert into mylock (name) values(‘d‘);
insert into mylock (name) values(‘e‘);
select * from mylock;
【手动增加表锁】
lock table tableName1 read/write, tableName2 read/write, 其它;
【查看表上加过的锁】
show open tables;
【释放表锁】
unlock tables;
我们为 mylock 表加 read 锁(读阻塞写例子)
我们为 mylock 表加 write 锁(MyISAM 存储引擎的写阻塞读例子)
案例结论
表锁分析
9.2.2 行锁(偏写)案例演示
事务(Transaction)及其 ACID 属性:
事务是由一组 SQL 语句组成的逻辑处理单元,事务具有以下 4 个属性,通常简称为事务的 ACID 属性。
原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如 B 树索引或双向链表)也都必须是正确的。
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的 “独立” 环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
并发事务处理带来的问题:
更新丢失(Lost Update):最后的更新覆盖了由其他事务所做的更新。如果在一个程序员在完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏读(Dirty Reads):事务 A 读取到了事务 B 已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果 B 事务回滚,A 读取的数据无效,不符合一致性要求。(读到了错误的数据)
不可重复读(Non-Repeatable Reads):事务 A 读取到了事务 B 已经提交的修改数据,不符合隔离性。(有个数据读多次,居然读的不一样)
幻读(Phantom Reads):事务 A 读取到了事务 B 体提交的新增数据,不符合隔离性。(读的数据变多了,出现了幻觉)
多说一句:幻读和脏读有点类似:
脏读是事务 B 里面修改了数据,
幻读是事务 B 里面新增了数据。
事务隔离级别:
脏读”、“不可重复读” 和 “幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
查看当期数据库的事务隔离级别命令:show variables like ‘tx_isolation‘;
隔离级别图解
建表 SQL
create table test_innodb_lock (a int(11), b varchar(16)) engine=innodb;
insert into test_innodb_lock values (1,‘b2‘);
insert into test_innodb_lock values (3,‘3‘);
insert into test_innodb_lock values (4,‘4000‘);
insert into test_innodb_lock values (5,‘5000‘);
insert into test_innodb_lock values (6,‘6000‘);
insert into test_innodb_lock values (7,‘7000‘);
insert into test_innodb_lock values (8,‘8000‘);
insert into test_innodb_lock values (9,‘9000‘);
insert into test_innodb_lock values (1,‘b1‘);
create index test_innodb_a_ind on test_innodb_lock (a);
create index test_innodb_lock_b_ind on test_innodb_lock (b);
select * from test_innodb_lock;
行锁定基本演示
索引失效行锁升级为表锁
间隙锁危害
面试题:如何锁应一行
案列结论
Innodb 存储引擎由于实现了行级锁定
,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于 MyISAM 的表级锁定的。当系统并发量较高的时候,Innodb 的整体性能和 MyISAM 相比就会有比较明显的优势了。
但是,Innodb 的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让 Innodb 的整体性能表现不仅不能比 MyISAM高,甚至可能会更差。
行锁分析
优化建议
第10章 MySql 主从复制
10.1 复制的基本原理
原理图
10.2 复制的基本原则 和 复制的最大问题
10.3 一主一从常见配置
以上是关于_逻辑架构的主要内容,如果未能解决你的问题,请参考以下文章
大数据技术之_29_MySQL 高級面试重点串讲_02_Mysql 简介+Linux 版的安装+逻辑架构介绍+性能优化+性能分析+查询截取分析+分区分库分表简介+锁机制+主从复制