MySQL相关知识点整理
2023-12-03 13:39:08 0 举报
AI智能生成
涵盖Mysql基础概念、相关日志的底层概念和调优方案等
作者其他创作
大纲/内容
MySQL
知识点
3 - 4 层的b+树足以支撑千万级别的数据量
MyISAM 不支持事务操作
加锁是给索引加锁
Join 本质 Nested-Loop Join
Index Nested-Loop Join
join的列是索引的情况
最好小表join大表
mysql内部会进行优化,所以不一定最后就是小join大
可以通过 STRAIGHT_JOIN 强制JOIN 顺序
Block Nested-Loop Join
非索引的情况
Join类型为all, index, range的时候才可以使用join buffer
引入了 join buffer、默认256k
查询的时候会将左表select的列和关联的列缓存到buffer中
这样join 右表的时候,就不需要多次IO左表的操作,直接到用buffer中的数据匹配右表的数据
join之前就会分配join buffer、query完成后就释放
Hash Join
Mysql 8.0.20 开始并删除了对嵌套循环的支持
join_buffer_size 可以对这个进行设置,如果超过了这个的限制,就会落到磁盘
如果创建的文件数量超过了 open_files_limit 的设定,就会导致连接不成功
join之前会将左表的列都进行hash处理,如hash(id),保存到内存总hash table
如果hash的内存超过 join_buffer_size ,就会通过hash算法将左表的列 hash 到不同的磁盘块上
这样即时从不同的磁盘块中Load的时候也要比直接从表中的列进行匹配的效率高,因为IO次数明显减少
外表和内表经过相同的hash函数处理后,必然会落在相同的分片中,接下来再对相同分片编号的数据进行 In-Memery Join( CMJ)
三大日志
Redo Log
归属Innodb
保证事务持久性
保证数据的可靠性
数据异常恢复和服务重启时页数据同步恢复
日志先行,在持久化数据文件之前,保证之气俺的redo已经写到磁盘
redo log 的大小是固定的,是循环写的过程
innodb_flush_log_at_trx_commit = 0|1|2
写日志
写日志的时候在用户空间有一个 log Buffer区域
对应在操作系统 内核空间有一个OS Buffer
0:每隔一秒把 log buffer 中的数据刷新到os buffer,并调用 fsync() 写入到log文件
1:提交事务的时候,把logbuffer中的数据刷到os buffer,并调用文件系统的写入操作,将缓存刷新到磁盘
2:提交事务的时候,将数据写入 os buffer,然后每隔1s 调用文件系统的flush操作
Undo Log
归属Innodb
作用:保证原子性,方便数据回滚
事务提交就删除
BinLog
归属 mysql server
记录语句的原始逻辑
SQL日志,用于数据恢复、数据同步等
对于支持事务的数据库来说,必须是提交了事务之后才会记录binlog日志,而binlog刷新到磁盘的时机和 sync_binlog 有关
sync_binlog
sync_binlog=0 : 事务提交后,不会立即将binlog cache中的日志信息刷到磁盘,二十依靠操作系统机制将缓存落盘,风险很大
sync_binlog=1 : 事务提交后,将二进制文件写入磁盘并立即执行刷新操作,相当于是同步写入磁盘,不经过操作系统的缓存
sync_binlog=n :每写n次操作系统缓冲就执行一次刷新操作
两阶段提交
数据更新到内层,写入redo,此时是 prepare 阶段
写入binlog后,提交事务,此时是commit 阶段
隔离级别
违反隔离级别的几个特征
脏读 : 当前事务中读取到了其它没有提交的事务修改的数据———读到了未提交的数据
不可重复读(针对修改或删除的情况): 同一个事务两次读取结果不一致
幻读(针对新增的情况): 幻读和不可重复读很像,同样是同一个事务中两次查找结果不一致现象,但是幻读只是针对于新增数据的情况
四种隔离级别
Read Uncommitted (读未提交) : 脏读、不可重复读、幻读 都没有解决、一般用不到
Read Committed (读已提交): 只解决了脏读的问题、没有解决不可重复读和幻读问题,是Oracle的默认隔离级别
Repeatable Read (可重复读) : 解决了脏读和不可重复读问题、但没有解决幻读问题,是Mysql的默认隔离级别
Serializable (串行化) : 脏读、不可重复读、幻读问题都有解决,但是很影响性能,一般也不用
事务隔离级别的实现方式
MVCC:Multi-Version Concurrent Control
数据库中每一个表最后面会隐藏三个列
TRX_ID 事务ID
ROOL_PTR 上一个版本undo log的地址
ROW_ID 如果没有主键或唯一索引的情况
可重复读 的read view 生成时在第一次select 语句执行的时候
不可重复读 的read view 生成实际是每次select 语句的时候
LBCC:Lock-Based Concurrent Control
锁的分类
共享锁 lock in share mode
排它锁 for update
意向锁(表锁) Intention Locks 类似一个标记,实际不是一个锁
自增锁(表锁) Auto-inc Locks
不是事务锁
通过参数innodb_autoinc_lock_mode控制
0:语句执行结束后才释放锁
1:普通insert语句,申请完锁后就释放、insert ... select 语句执行结束后释放,保证顺序
2:申请后就释放锁
锁的使用场景
表锁
修改数据库表结构会自动增加表级锁
更新数据未使用索引,行锁上升为表锁:如 update xxx set xxx where name = 'xxx' , name 没加索引会锁定表
行锁
更新数据使用索引,会使用行级锁
select ... for update 会使用表级锁
锁的算法(Innodb)
记录锁(Record Locks):锁当前记录
间隙锁(Gap Locks):锁一个范围
临键锁(Next-Key Locks): 左开右闭,锁记录+范围
注:是根据记录锁区间、不是根据where条件锁区间
知识点
锁的是索引
子主题
show profile
作用:查看SQL的执行速度和相关性能
默认不开启、通过set profiling=1 开启
查看用户CPU时间、系统CPU时间
查看SQL的详细执行信息、执行时间等
该方式在外来的版本中会被废除(来自Mysql官网)
Performance Schema(87张表)
作用:监控MySQL server在一个较低级别的运行过程中的资源消耗情况
监控信息会保存在当前 performance_schema的库中
该数据库主要关注数据库运行过程中的性能相关的数据,与information_schema不同,information_schema主要关注server运行过程中的元数据信息
performance_schema的表中的数据在内存中,服务重启就消失
对应的事件采集配置默认可能不是打开的,需要配置
使用方式和正常的sql查询一样,通过select 的方式查看对应的事件
可以做啥(具体细节看github文档或者Notion笔记)
查看哪类的SQL执行最多?
查看哪类SQL的平均响应时间最多?
查看哪类SQL排序记录数最多?
查看哪类SQL扫描记录数最多?
查看哪类SQL使用临时表最多?
查看哪类SQL返回结果集最多?
查看哪个表物理IO最多?
查看哪个表逻辑IO最多?
哪个索引访问最多?
哪个索引从来没有用过?
哪个等待事件消耗时间最多?
剖析某条SQL的执行情况,包括statement信息,stege信息,wait信息
查看每个阶段的时间消耗
查看每个阶段的锁等待情况
使用show processlist查看连接的线程个数
会展示当前的一些状态
查询信息
等待信息
执行时间
执行状态
锁的状态
等等等等
优化
表结构、数据结构和分区表的优化
数据类型优化
更小的数据类型,因为分配内存空间是通过数据类型分配的
数据类型越简单越好,能用数字就不要用字符串
存储空间小
计算快
简单
尽量避免NULL,对于mysql来说很难优化,因为允许null的列使得索引、索引统计和值的比较更加复杂
尽量用满足需求的最小长度
varchar在mysql5.6之前变更长度,或者从255一下变到255以上时,会导致锁表
尽量避免 BLOB 和TEXT 的使用,两者分别采用二进制和字符串的方式存储
当表中 存在TEXT或者很大的varchar的时候,
可以考虑将这个字段和表中的其他字段做拆分处理,这样其它表中查找的性能会高一点
可以考虑将这个字段和表中的其他字段做拆分处理,这样其它表中查找的性能会高一点
使用枚举类型替代字符串
特殊类型数据存储方式,如IP的转换函数 inet_aton/inet_ntoa
小表 join 大表
分区表(目的:减少IO量)
相同类型的数据会聚集在一个文件中
查找的时候根据创建条件判断在那个文件中,不需要扫描所有数据
分而治之
创建语法 partition by
应用场景
冷热数据分离
部分场景下更容易维护,如:批量删除同类型的数据可以采用清除分区的方式
备份恢复独立分区等等
分区表限制
单表最多1024个分区,5.7版本的时候可以支持8196个分区
分区表无法使用外键约束
如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
分区表类型
范围分区
列表分区
列分区
hash分区
key分区
子分区(在分区的基础之上,再进行分区后存储)
分区表注意事项
null值会使分区过滤无效
分区列和索引列不匹配,会导致查询无法进行分区过滤
- 打开并锁住所有底层表的成本可能很高
打开并锁住所有底层表(CURD的情况)的成本可能很高
维护分区的成本可能很高
执行计划查看(explain “sql”)
id 列:查询中执行select子句或者操作表的顺序
id 相同:执行顺序从上到下
id 不同:id 越大,优先级越高,先被执行
id 有相同的,有不同的:相同的视为一组,不同组id越大越先执行
select_type 列:分辨查询的类型,是普通查询还是联合查询还是子查询等
table 列:对应行正在访问哪一个表,表名或者别名,可能是临时表或者union合并结果集
type 列:访问类型,优化SQL的重要指标
优先级顺序(最好到最坏):system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
all:全表扫描
index:全索引扫描这个比all的效率要好
range:表示利用索引查询的时候限制了范围,在指定范围内进行查询,这样避免了index的全索引扫描
index_subquery:利用索引来关联子查询,不再扫描全表
unique_subquery: 该连接类型类似与index_subquery,使用的是唯一索引
index_merge:在查询过程中需要多个索引组合使用
ref:使用了非唯一性索引进行数据的查找
eq_ref :使用唯一性索引进行数据查找
const:这个表至多有一个匹配行
system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现
主要关注如下几个即可: system > const > ref > range > index > ALL
possible_keys 列:显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key 列:实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠
key_len 列:表示索引中使用的字节数,满足需求下越短越好
ref 列:显示索引的哪一列被使用了
rows 列:根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的sql找了多少数据,在完成目的的情况下越少越好
extra 类: 包含额外的信息。
using index:使用了索引覆盖
using index condition:使用了索引下推
using filesourt:使用了临时空间排序,没有用索引排序
索引
优点
a、大大减少了服务器需要扫描的数据量
b、帮助服务器避免排序和临时表(索引已经排好序了)
c、将随机io变成顺序io
索引的分类
1、主键索引
2、唯一索引
3、普通索引
4、全文索引(一般是针对varchar或者text字段的)
5、组合索引
技术名词
1、回表(普通索引)才会有回表的情况
根据普通索引找到子节点中存储的主键,然后根据这个主键再到聚集索引定位记录
根据普通索引找到子节点中存储的主键,然后根据这个主键再到聚集索引定位记录
2、覆盖索引
select id,name from user where name='a'; id 主键索引 name普通索引
explain的输出结果Extra字段为Using index时,能够触发索引覆盖
select id,name from user where name='a'; id 主键索引 name普通索引
explain的输出结果Extra字段为Using index时,能够触发索引覆盖
3、最左匹配
4、索引下推(组合索引) ————— 先在索引层面做一次判断,进行一次过滤
SELECT * from user where name like '陈%' and age=20 index(name,age)
5.6 之前是查询到 "陈%"后就回表,mysql 5.6后 过滤掉age后 再回表,这样就会提前过滤掉一部分数据
SELECT * from user where name like '陈%' and age=20 index(name,age)
5.6 之前是查询到 "陈%"后就回表,mysql 5.6后 过滤掉age后 再回表,这样就会提前过滤掉一部分数据
索引匹配方式
全值匹配
匹配最左前缀
匹配列前缀
匹配范围值
精确匹配某一列并范围匹配另外一列
只访问索引的查询
优化细节
当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
使用前缀索引
使用索引扫描来排序————— 因为索引已经排好序了
union all,in,or都能够使用索引,但是推荐使用in—— 执行计划基本一样,效率也差不多,但是in更好一点
范围列可以用到索引
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
强制类型转换会全表扫描
更新十分频繁,数据区分度不高的字段上不宜建立索引
创建索引的列,不允许为null,可能会得到不符合预期的结果
当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
能使用limit的时候尽量使用limit
单表索引建议控制在5个以内
单索引字段数不允许超过5个(组合索引)
索引监控
命令:show status like 'Handler_read%';
Handler_read_first:读取索引第一个条目的次数,读取根节点一共读取了多少次
Handler_read_key:通过index获取数据的次数
Handler_read_last:读取索引最后一个条目的次数
Handler_read_next:通过索引读取下一条数据的次数
Handler_read_prev:通过索引读取上一条数据的次数
Handler_read_rnd:从固定位置读取数据的次数
Handler_read_rnd_next:从数据节点读取下一条数据的次数
注意:应该主要关注 Handler_read_key Handler_read_rnd_next ,如果这两个值越大越好,越小说明索引使用率很差
索引失效的情况
like 以%开头的情况
索引列使用了计算,比如 where age + 10 = 30 不走索引,但是 where age = 30-10 走索引
索引列使用了函数会导致索引失效,比如 where concat(name,'abc') = 'xxxxabc' 索引失效
索引列和=后面的值存在类型转换的情况
索引列是char,值是数字:会失效
索引列是int,值是字符串:不会失效,但是有开销
服务器配置相关(Notion笔记)
主从复制
原理
原理图
1、master 写日志到binlog
2、slave会在一定时间间隔内探测master中的日志有无发生变化
3、如果改变、则开启一个IO Thread 请求master 二进制事件
4、收到请求后,master 会为每一个IO Thread 启动一个dump 线程,用于发送二进制事件(顺序读)
5、slave 收到二进制文件之后会保存 到中继日志(relay log) (顺序写)
6、slave 将启动 SQL Thread 从relay-log中的日志,在本地重放同步数据,使得保持一致(随机写)
7、IO Thread 和 SQL Thread 进入休眠状态、等待下一次被唤醒
配置步骤
主从配置
1、配置各自服务的my.conf 文件
2、在master中,授权对应的slave (grant 命令),允许主从复制的IP地址等
3、在slave中,配置连接信息,从master中进行数据拉去,通过 change master to ..... 进行配置
4、show slave status 查看slave状态(Slave_IO_Running和Slave_SQL_Running状态都是YES的时候就正常了)
5、启动slave ,命令start slave
主备配置(双主双从)
1、先分别配置两个主从,master1和slave1、master2和slave2,步骤同上一样
2、在master1中执行change master to... (省略的信息master2中的Host,user,pwd等)
3、同样在master2中执行change master to ... (省略的信息master1中的Host,user,pwd等)
原来的配置方式
change master to master_host='192.168.85.11',master_user='root',master_password='123456',master_port=3306,master_log_file='master-bin.000001',master_log_pos=154;
基于GTID的配置
添加配置
gtid_mode=on
enforce-gtid-consistency=true
gtid_mode=on
enforce-gtid-consistency=true
从库执行如下命令即可
change master to master_host='192.168.85.111',master_user='root',master_password='123456'
,master_auto_position=1;
主从复制延时问题
延时分析(为什么会延时)
1、主从复制都是单线程操作
2、master 读完后向 slave 中继日志(relay-log)写入的整个过程都是顺序读写操作,所以效率取决于设备和带宽等外界因素
3、在slave中,SQL 从relay-log 重放日志的时候 DML 和 DDL 的IO 操作都是 随机的,所以成本很高
4、另外 SQL Thread 也是单线程的,所以当主库的并发较高时,Slave 的SQL Thread 的压力就会增大、会产生锁等待
5、大事务
6、锁资源的抢占
延时问题解决方案
1、分库架构、分散压力
2、读写分离、主写从读
3、加入缓存、如redis,减少数据库的读压力
4、数据库分散在不同的机器
5、提升硬件设备
6、组提交
组提交思想
redo log
将多个redo log 的刷盘动作合并,减少磁盘顺序写的次数
每条redo log 都有一个LSN ( Log Sequence Number ),LSN 是单调递增的
各个事务将 日志 拷贝到log_sys_buffer 时都会获取当前的最大 LSN
假设有 A B C 三个事务(顺序执行的) LSN 依次是 100,200,300
当A事务进行落盘的时候,会将 100、200、300 LSN 这一组redo log 一并落盘
假设有 A B C 三个事务(顺序执行的) LSN 依次是 100,200,300
当A事务进行落盘的时候,会将 100、200、300 LSN 这一组redo log 一并落盘
bin log
以前版本是不支持 binlog 的组提交方式的
原因是 redo 和 bin log 的刷盘串行化(保证两份日志一致性)问题
原因是 redo 和 bin log 的刷盘串行化(保证两份日志一致性)问题
mysql 5.6 版本之后引入了队列机制,保证了commit 顺序和binlog 落盘顺序一致,
并将事务分组,组内binlog刷盘交给一个事务进行,实现组提交目的,组内的binlog刷盘动作交给一个事务进行,实现组提交的目的
并将事务分组,组内binlog刷盘交给一个事务进行,实现组提交目的,组内的binlog刷盘动作交给一个事务进行,实现组提交的目的
binlog提交将提交分为了3个阶段,flush阶段,sync阶段和commit阶段
每个阶段都有一个队列,每个队列有一个mutex保护,预定进入队列的第一个线程为leader,其他线程为follower,所有事情交给leader去做,leader做完所有的动作之后,通知follower刷盘结束
如果想要提高binlog组提交的效率的话,那么可以通过设置一下两个参数
binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用fsync;
binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync。
7、mysql 5.6开始就使用MTS 并行复制技术解决延时问题
原理
增加了 Coordinator 组件,原来的 SQL Thread是单线程
日志来了之后 coordinator 负责读取以及分发事务
真正的执行过程放在了worker 线程 (多个worker线程并行处理)
Coordinator 分发的时候遵循的策略
更新同一行的两个事务必须要分配在同一个Worker
同一个事务不能被拆开,必须放在一个worker中
分发方式
通过在每一个worker 中定义一个hash表,用来存储正在执行事务设计的表
hash 表的 key 按照不同的力度存储不同的值
1、按库分发:key 为 数据库名字(mysql 5.6 就是按库并行的)
2、按表分发:key 为数据库名字+表名
3、按行分发:key 为 数据库名+表名+唯一键
mariaDB的并行复制策略利用的就是这个特性
能够在同一组里提交的事务,一定不会修改同一行
主库上可以并行执行的事务,备库上也一定是可以并行执行的
8、mysql5.7的并行复制策略
根据mariaDB的并行复制策略,做了相应的优化调整
可以通过参数slave-parallel-type来控制并行复制的策略
可以通过参数slave-parallel-type来控制并行复制的策略
1、当配置的值为DATABASE的时候,表示使用5.6版本的按库并行策略;
2、当配置的值为LOGICAL_CLOCK的时候,表示跟mariaDB相同的策略。
5.7 的并行复制策略
1、同时处于prepare状态的事务,在备库执行是可以并行的
2、处于prepare状态的事务,与处于commit状态的事务之间,在备库上执行也是可以并行的。
怎么做的???
1、提高binlog组提交的效率的话,可以设置如下两个参数
binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用fsync;
binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync。
2、mysql 5.7 的并行复制策略,可以设置如下参数
slave-parallel-type来控制并行复制的策略
参考:https://blog.csdn.net/weixin_30877755/article/details/96745861
主从复制可能出现的额问题
当slave 允许写的时候,slave中某一个表人为干预写入一个后,同步就会出错
这个时候 IO Thread 和SQL Thread 就会变成NO,Slave 就会stop掉
这种情况就需要删除数据库重新全量进行拉取操作
所以在生产中就不应该允许slave 进行写入操作
主从不一致或者同步出问题后应怎么重新配置
通过 show master status / show slave status 查看 position 是否一致
reset master、reset slave 重置后重新配置
实现方案
Mycat
Sharding JDBC
0 条评论
下一页