关于MySql的知识点记录
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于MySql的知识点记录相关的知识,希望对你有一定的参考价值。
文章目录
- 1.B树与B+树的基本区别?
- 2.InnoDB的B+树是如何产生的呢?
- 3. InnoDb查询怎么用到索引的呢?
- 4.关于联合索引必须遵守最左前缀的问题
- 5.范围查找导致索引失效的问题
- 6.关于覆盖索引
- 7. order by 为什么会失效
- 8.Mysql中的数据转换注意点;
- 9. 关于MySql的存储引擎
- 10.关于数据表的字段设计
- 11.关于MySQl的索引
- 12. 如何优化插入数据的性能
- 13.全局锁,共享锁,排他锁
- 14.关于MySql的死锁
- 15.InnoDb是如何实现事务的呢?
- 16.MySql的集群搭建知识,读写分离
- 17.对于MySql慢查询的优化
- 18.数据库索引失效场景
- 19.海量数据中,如何快速查找到一条记录
- 20.关于MVCC
- 21.Explain语句执行后结果的字段含义
前言:
最近在B站看到的一段学习视频,
视频原链接,
进行了学习记录.
1.B树与B+树的基本区别?
首先看看将[1,2,3,4,5,6,7,8,9] 存入B树和B+树之后的结构
推荐使用工具:数据结构可视化网站
B树
B+树
可以看到
B+树
的叶子节点之间有指针
都知道mysql默认使用Innodb引擎,那么去官网看看吧->
MySql官网
进入到--------->innodb-page-structure ,查看页结构的说明;
最终看到这样一段说明:
大概翻译:
每个人都见过 B 树,都知道根页面中的节点指向叶子节点。 (我在图中用垂直“|”条表示那些指针。)但有时人们会忽略叶子节点也可以相互指向的细节(我用水平双向指针“<–>”表示这些指针) 图)。 此功能允许 InnoDB从一个叶子指向到另一个叶子
,而无需备份到根级别。 这是您在经典 B-tree 中找不到的复杂性,这就是为什么 InnoDB 应该被称为使用 B+tree 的原因。
这也就是为什么Mysql的InnoDB引擎要使用B+树了;
- B+树的非叶子节点仅存储索引的key值,而具体数据是存到叶子节点中。
- B+树的
数据都存储在叶子结点中
,只需要扫一遍叶子结点即可,而B树由于非叶子结点同样存储着数据,若要找数据就得全树查询,这也就是B+树为啥更适合于区间查询.- B+树的查询效率更加稳定,数据查找时必须走从根结点->叶子结点。
2.InnoDB的B+树是如何产生的呢?
首先在SQLYog工具看看一些参数;
# 查看Innodb默认的页(节点)大小;
SHOW GLOBAL STATUS LIKE 'Innodb_page_size';
大概转换后:
16KB
#转换计算;
SELECT 16384/1024;
Innodb这个向磁盘读数据/写数据,最小单位就是一页; 开辟16Kb的内存空间,一页一页的使用;
创建一个测试表进行学习.
# 创建测试使用的数据表;
CREATE TABLE `test1`(
`a` INT PRIMARY KEY,
`b` INT,
`c` INT,
`d` INT,
`e` VARCHAR(20)
)ENGINE=INNODB;
# 查看索引;
SHOW INDEX FROM test1;
添加一些数据
# 添加部分数据;
INSERT INTO test1 VALUES(4,3,1,1,'d');
INSERT INTO test1 VALUES(1,1,1,1,'a');
INSERT INTO test1 VALUES(8,8,8,8,'h');
INSERT INTO test1 VALUES(2,2,2,2,'b');
INSERT INTO test1 VALUES(5,2,3,5,'e');
INSERT INTO test1 VALUES(3,3,2,2,'c');
INSERT INTO test1 VALUES(7,4,5,5,'g');
INSERT INTO test1 VALUES(6,6,4,4,'f');
不加条件的查询;
SELECT * FROM test1;
可以看到查询出来的结果,默认是按照字段a的数据升序输出了.
那么现在如果我用进行条件查询呢,
# 加条件的查询;
SELECT * FROM test1 WHERE a=6;
那么实际上还是要将表记录一条一条地查过去,最终查到
a=6
的记录进行返回;
接着看看刚才建表时的设计,一行记录的话也不会超过20字节;
刚才也提到了,数据库存取数据是一页一页的使用,而一页16kb;那么具体的在这个表中,就这8行数据也就一次放到一页中取过来了;
一次IO操作从磁盘中取出数据存入到内存中,然后CPU一行一行地取;
具体可以这样去看
- 假如现在仅存了三条数据;现在想要查找
a=3
的数据,实际上找到第二行数据就可以断定数据库没有想要的数据了,- 注意这个用户数据区域是逻辑上的连续空间,每个数据之间都有指针相连.
- 在这个数据的存储时,由于要考虑连接的位置问题,所以效率上也有一定损失,那么建议使用自增的主键的话,那就自己在后面拼接了,不考虑断开指针,重连指针的问题.
那么咱们作为Java开发者,可以将这个页想象成一个类,里面存放了List属性的用户数据记录;
class Page
List<UserRecord> lists;
在官方文档的解释中,关于用户记录数据的说明
现在想一想书籍的目录是怎么设计的,对于一些相似的内容,会分为一章一章的内容;那么就可以很快的地找到目标内容;
那么在具体的页中,也会有这样的目录来记录用户数据记录,对应用户数据的分组,
目录存放的是起始数据;
比如目录1
对应 用户数据记录中 a=1和a=2分组的数据;
目录2
对应 用户数据记录中 a=4和a=8分组的数据;
比较经典的空间换时间策略.
- 假如说数据现在这一页数据满了,要存入新数据,那么就得新开一个页,
- 现在这么一个情况,数据
a=5
这一行本来要存到数据a=4
的后面,那么就得牵扯到将数据a=8
这行换出放到新开的数据页,- 所以建议使用自增的索引;
那么模拟一下这8条数据的存储,可以这么看;
- 实际上,对于这些页,还存在着指针的联系;
比如你想查询a=60000的数据,那么就可以去找目录起始为37的页;- 其实在向上延伸的话,你就会发现这个就是B+树的结构了.
3. InnoDb查询怎么用到索引的呢?
就以之前的8行数据记录进行模拟,可看到主要的数据是存放在叶子节点的;
这里的a字段用了主键索引; 其实主键索引存在的话,
即可引出 聚簇索引
;
比如说要找这个
a=6
的数据;由上往下查,即可很快查到;
而如果现在要以
b=6
作为条件查数据,怎么查呢?
(注意之前没有给字段b使用索引哦);
- 那么就需要全表扫描查询了
那么对于
范围查找
,当然就是根据索引查到两边等值的节点数据,然后返回中间的节点数据即可;
4.关于联合索引必须遵守最左前缀的问题
首先对 b,c,d三个字段,建立联合索引
# 为 b,c,d字段创建联合索引;
CREATE INDEX idx_t1 ON test1(b,c,d);
那么对于bcd这个联合索引,;
- 如果按照这样的结构进行存储,对于空间实际上是有一些浪费的;
- 由于 字段e目前是没有使用索引的 ,那么你想改e字段的某个数据值,还在这样的结构中,
那么改成这样的结构呢?
这里仅存储 bcd字段的数据;
若已 bcd 的值作为条件进行查询时,这样的结构还不能达到要求,因为查询的目标是找到所有的数据;
那么在找到 节点 111
中的数据后,要以他为条件找到表记录数据
EXPLAIN SELECT * FROM test1 WHERE b=1 AND c=1 AND d=1;
实际上这里还要存主键的索引,按条件找到主键后,再根据主键去回表查询到需要的数据
如果我将条件换成这样呢?
可能你觉得他不会根据索引查,但实际上它遵循了最左前缀原则;
如果查询条件中去除 b字段,这时就不会使用索引了.
- 因为这样的话,查询条件可以看做
*11
,自然是无法使用索引了;
若使用b和d字段作为条件,注意包含了b字段哦;
查询的话相当于查询条件为1*1
,即可使用到索引
5.范围查找导致索引失效的问题
比如说查询
b>1
的数据
EXPLAIN SELECT * FROM test1 WHERE b>1;
这里若使用查询
b>1
的数据,若使用索引的话,他要考虑7次回表查询所有数据的问题,
那么效率还比不上直接全表查询.
而将查询条件改为 b>5去查询;
EXPLAIN SELECT * FROM test1 WHERE b>5;
就会使用到索引,因为此时回表查询比全表扫描查询的效率高一点;
这也就是为什么部分情况下范围查询会失效的情况
6.关于覆盖索引
- 比如要查询按b字段进行范围查询, 最终返回所有符合的b字段值;
- 此时就是覆盖索引;
- 需要查询的数据被索引覆盖;
- 此时就不需要进行回表查询了.
7. order by 为什么会失效
- 假设使用了索引,实际上不需要排序, 由于查询select e… ,需要进行回表查询;
- 这里显然没使用索引,即这里用了order by之后,索引失效了;
- 而这里实际使用了全表扫描,因为在内中的每一页中需要维护一个额外的排序;
可以注意的是,这里若对b字段进行查询,发现order by并没有让索引失效,
实际是用到了覆盖索引,即这里不会触发回表查询
8.Mysql中的数据转换注意点;
可以看一下数据库的字符集类型;这里核对可以看做是不同的规定;
比如在某个规定中字符a>b;
而在某个规定中字符a<b;
即代表不同的规则
这里还记的之前的字段e
是varchar类型的
;
我想为字段e创建索引;那么
# 为e字段创建索引;
CREATE INDEX idx_te ON test1(e);
试试不同的sql是否可走索引;
注意到这个使用a字段
指定字符,也是可以查询的,虽然之前a字段的数据类型是 int
但是一旦对 字段e
使用了数字类型作为条件,此时不可按索引;
进行了全表扫描的查询;
(注意字段e是 varchar字符类型的)
可进行这样一个测试;
SELECT 'a' =0;
SELECT 'b' =0;
得出结果都是1;表示为真;
即认定字符a
和字符b
都对应 0;
而对于数字字符,会对应数字
SELECT '527' = 527;
回到之前这个查询问题,为什么没有走索引,
EXPLAIN SELECT * FROM test1 WHERE e=1;
由于字段e是字符类型的,而用 e=1进行使用时,涉及到类型转换问题,即对表中的数据’'a" =1 ,“b=1”…逐个替换,这样使用索引的代价过大,直接进行全表插叙效果会好一点;
还有一点要注意的是,不要在查询条件中对字段进行计算操作,同样会使得索引失效;
9. 关于MySql的存储引擎
MySQL数据库最重要的特点就是其插件式的表存储引擎。
每个存储引擎都有各自的特点,能够根据具体的应用使用不同的存储引擎,
Innodb 引擎:
默认的mysql引擎,可处理事务,基于聚簇索引建立。支持事务和崩溃修复能力;支持外键约束,支持表级锁
以及行级锁
;
MyISAM引擎;
而在mysql5.1版本之前,默认使用的是myIsam作为引擎的;
而MyISAM不支持事务,不能用外键,仅可用表级锁,无法使用行级锁;
但是访问速度快.
MEMORY引擎
将数据全部放在内存中,访问速度较快,但是系统奔溃时数据会丢失。
默认使用哈希索引,将键的哈希值和指向数据行的指针保存在哈希索引中,那么它无法支持排序,也无法支持范围查找.
注意Innodb 引擎 没有存储表的具体行数,若要查行数就得全表扫描;
而MyISAM引擎 使用了一个变量存储表的具体行数,查行数时返回变量值即可;
10.关于数据表的字段设计
优先级的话,考虑
整型 <-- date,time <-- enum char <-- varchar <--blob,text
-
建议优先使用字段长度最小, 优先使用顶层类型的,
-
在数值型的字段中尽量不用 “zero fill”
-
time: 定长,效率号,但需考虑时区;
-
enum:内部使用了整型,但是若要和char一起联合查询时,内部会完成串-值的转换;
-
char : 定长,需要考虑字符集;
-
varchar
:不是定长的,也要考虑字符集; 最多可以定义65535个字节,但实际并不是存储这么多;其中需要1到2个字节来存储数据长度
(若列声明的长度超过255
,用两个字节来存储长度,否则1个字节). -
text,blob:存储容量大,不能使用内存临时表,排序操作是在磁盘上进行的;
-
在定义字段时,根据实际情况设定空间长度大小,放置过多空间闲置,使用时效率也不好;
-
尽量不要使用null进行存储;
-
注意根据实际情况选择char和varchar, char类型是定长的,处理速度相对快一点,但是会有空间浪费,而varchar是不定长的,但速度比不上char.
11.关于MySQl的索引
mysql的索引按照字段特征分类,可以分成 主键索引
,普通索引
,前缀索引
.
- 建立在主键上的索引称为
主键索引
,一张数据表仅可有一个主键索引,主键不能为空; - 建立在
unique
字段的索引即唯一索引
,在一个数据表中可以创建多个唯一索引,而索引的列值允许为空,唯一索引允许包含空值的列有多个空值。
因为这里 NULL 的定义 ,是指
未知值
。 所以多个 NULL ,都是未知的,不能说它们是相等的,也不能说是不等,就是未知的。所以多个NULL的存在是不违反唯一约束的。
- 在普通列上建立的就是
普通索引
; 前缀索引
就是 对于字符类型字段上的前几个字符
或者是二进制类型字段的前几个字节建立的索引,并不是对整个字段建立索引, 类型: char,varchar,binary,varbinary都可以,使用前缀索引
可以节约存储空间,但是缺点:无法用前缀索引进行order by 操作与 group by操作
.
三星索引
要是在查询时使用三星索引
,一次查询通常只需要进行一次磁盘的随机读以及一次窄索引片的扫描,因为
它的响应时间比使用普通索引少.
一个查询相关的索引行是相邻的或至少相距足够近的获得一星,
如果索引中的数据顺序和查找中的排序一致则获得二星,
若索引中的列包含了查询中需要的全部列则获得三星
12. 如何优化插入数据的性能
-
(1)可以合并多个inser 语句;
合并之后,在mysql中的binlog
二进制记录日志量就会减少,同时关于事务的日志也会减少,降低了日志在使用时的刷新磁盘的数据量以及频率,提升性能.
同时也会减少sql语句的解析频率,减少网络传输的IO操作. -
(2)可以修改 bulk_insert_buffer_size参数,加大插入的缓存.
-
(3)可以设置参数 innodb_flush_log_at_trx_commit =0
参数值释义–>
0
: 将log buffer中的数据每秒一次频率写入日志文件中,同时进行文件系统到磁盘文件的同步,但每个事务的commit并不触发log buffer到日志文件的刷新或者文件系统到磁盘的刷新操作;
1
: 在每次事务提交时,将log buffer数据写入日志文件,触发文件系统到磁盘文件的同步;
2
:事务提交时,将log buffer数据写入日志文件,但是不会进行
文件系统到磁盘文件的同步;每秒会进行一次文件系统到磁盘的同步. -
(4) 修改为手动使用事务;
由于mysql默认自动提交事务,每次插入数据就会触发commit
,所以为减少事务的消耗,可改为手动的.
13.全局锁,共享锁,排他锁
全局锁:对于整个数据库进行加锁,具体可以为全库做逻辑备份;
- 可以让数据库处于
只读状态
,使用命令后,数据更新语句
,数据定义语句
,更新类事务
的提交语句操作都会被阻塞.
共享锁: 读锁 /S锁,多个事务可以并发读取数据,但是任何的事务都不能进行修改,直到释放所有共享锁.
排他锁: 写锁/X锁, 若事务对某行数据加了排他锁,就只能由这个事务进行读写操作,其他事务不可进行加锁.
14.关于MySql的死锁
死锁是指2/2个以上的进程在执行过程中,由于抢占同一块共享资源而造成的相互等待.
查看死锁:
show engine innodb status
可查到最近的一次死锁,
innodb lock monitor
打开锁监控, 每15秒输出一次日志,使用结束后建议进行关闭,防止影响数据库的性能.
解决策略
可以使用 innodb lockwait_timeout 设置超时的时间,一直等待直到超时;
可以发出死锁检查,出现死锁后,可以主动回滚死锁的某个事务,使得其他事务继续执行.
15.InnoDb是如何实现事务的呢?
InnoDb引擎会通过缓冲池,日志缓存,redo log
(重做日志),undo log
(回滚日志);
比如这样一个更新操作的步骤:
- 首先Innodb引擎收到update更新语句,根据条件找到数据所在的页,将当前页存入到缓冲池中;
- 执行update更新语句,修改缓冲池中的数据,即内存中的数据;
- 针对update语句生成
redo log
对象.,存入日志缓存中; - 对 update语句生成
undo log
回滚日志; - 若事务提交,则将
redo log
对象进行持久化,后面也会将缓冲池中修改的数据页持久化到磁盘中; - 若事务回滚,则使用
undo log
日志进行回滚;
16.MySql的集群搭建知识,读写分离
MySql将主节点的binlog日同步给子节点,完成主从的数据同步.
注意
只有主节点可以将自己的binlog日志同步给子节点.
由于要保持主从之间的数据一致,写操作
仅可在主节点完成,而读操作
可以在主/从机进行.
查看主节点的binlog日志位置
查看从机的状态;
MySQL主从集群默认采用
异步复制机制
。主服务在执行用户提交的事务后,写入binlog日志,然后就给客户端返回一个成功的响应。而binlog会由一个dump线程异步发送给Slave从服务。
由于发送binlog过程异步
。
主服务在向客户端反馈执行结果时,无法得知binlog是否同步成功。
如果主服务宕机,而从服务还未备份到新执行的binlog,有可能丢数据。
半同步复制机制
介于异步复制和全同步复制之间。
主库在执行完客户端提交的事务后,并不是立即返回客户端响应,等待至少一个从库接收并写到relay log
中,才会返回给客户端。
MySQL在等待确认时,默认会等10秒,如果超过1 0秒没有收到ack,就会降级成为异步复制。
- 相比异步复制,可提高数据的安全性。但安全性不是绝对的,仅保证事务提交后的binlog至少传输到了一个从库,并且并不保证从库应用这个事务的binlog是成功的。
- 半同步复制机制也会造成部分延迟,延迟时间最少是一个TCP/IP请求往返的时间。
17.对于MySql慢查询的优化
- 可优化sql使用索引;
- 可检测是否为最优索引;
- 检测查询的结果字段都是必须的,是否查询了冗余数据;
- 检查表数据是否过多,考虑分库分表;
- 检查数据库的性能配置
18.数据库索引失效场景
- 不符合最左前缀原则
- 字段进行了隐式数据类型转换
- 索引效率低于全表扫描效率
- 当出现以
%
开头的like模糊查询,例如%小杰
,无法使用索引; - 但是若不是
%
开头的,可以走索引,比如小杰%
,相当于范围查询;会用到索引. - 若查询的条件中,列的类型是字符串,没有使用引号,可能因为类型不同发生隐式转换,使得索引失效.
- 当查询判断索引类是否
不等于
某个值,索引失效; - 对索引列进行计算时,索引失效;
- 查询条件若使用
or
,则索引会失效;
19.海量数据中,如何快速查找到一条记录
- 可考虑使用布隆过滤器,过滤不存在的记录数据.
使用redis的bitmap结构实现布隆过滤器;
- 在redis中创建数据缓存
可以使用普通的字符串存储:例如 userId : user.json;
可以使用hash存储:例如使用userId做key,其他数据作为field;
或者使用整个hash存储数据:userInfo作为1key; 使用userId作为field;user.json作为value.
一个hash 支持 232 -1 个键值对;
20.关于MVCC
多版本并发控制,在读取数据时通过快照机制将数据保存,这样的话读锁与写锁不冲突,不同的事务session会看到自己特定版本的数据
,版本链
;
注意MVCC仅在 RC(读已提交)
和 RR(可重复读)
级别下工作;
因为RUC(读未提交)级别总是读取最新的数据,而不是符合当前事务版本的数据行,而串行化级别会对所有的读取数据行加锁.
注意聚簇索引记录的两个重要隐藏列:
tx_id
: 存储每次对于某个聚簇索引记录修改时的事务id
;
roll_pointer
:每次对于哪条聚簇索引的记录修改时,都会将老版本的写入到回滚日志 undo log
,而roll_pointer
作为回滚指针,会指向到这个记录的上一版本位置,注入插入操作没有 该操作,由于没有老版本.
在开始事务时创建临时读视图
readview
,这个readView 可维护当前活动的事务id,即没有提交的事务id,排序生成一个数组进行数据访问,获取数据中的事务id(可以获取到事务id的最大记录);
- 若在readView的左边, 则可以访问;(代表该事务已经提交).
- 若在readView的右边/在readView中,不可以访问,获取到
roll_pointer
,取出上一个版本进行重新对比,(在右边意味着该事务在readView生成之后出现,在readView中意味着该事务还未提交);
注意:在RC读已提交
级别下的事务,每次查询开始都会生成一个独立的readView,
在RR可重复读
级别下,是在第一次读的时候生成一个readView,之后的读取都用的是这第一次的快照读.
21.Explain语句执行后结果的字段含义
以上是关于关于MySql的知识点记录的主要内容,如果未能解决你的问题,请参考以下文章