mysql
mysql
show variables like 'binlog_format' 查看binlog 格式
show variables like 'log_bin' binlog 是否开启
show binary logs 获取binlog列表
show master status 当前正在写入的binlog文件
show binlog events in 'ON.000056' 查看指定binlog文件的内容
- Statement 保存ddl语句
- Row 保存改动后记录
- Mixed
全量备份数据
mysqldump -uroot -p -B -F -R -x --master-data=2 test > d:/backup.sql
-B 指定数据库 -F 刷新日志(刷新创建新的binlog日志,用来记录备份之后数据库增删改flush logs )
-R 备份存储过程 -x 锁表
从binlog日志恢复数据
恢复命令的语法格式:
mysqlbinlog mysql-bin.0000xx | mysql -u用户名 -p密码 数据库名
常用参数选项解释:
--start-position=875 起始pos点
--stop-position=954 结束pos点
--start-datetime="2016-9-25 22:01:08" 起始时间点
--stop-datetime="2019-9-25 22:09:46" 结束时间点
--database=xxx 指定只恢复xxx数据库(一台主机上往往有多个数据库,只限本地log日志)
Explain 详解
explain 字段 含义
id 执行编号,值越大越先执行
select_type 显示查询的类型,比如简单查询还是子查询等
table 访问引用哪个表(引用某个查询,如"derived1")
type 数据访问/读取操作类型(ALL、index、range、ref、eq_ref、const/system、NULL)
possible_keys 列出哪一些索引可能有利于高效的查找
key 显示mysql在本次查询中决定使用的索引
key_len 使用到的索引长度
ref 显示了之前的表在key列记录的索引中查找值所用的列或常量
rows 预估的需要读取的记录条数
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比
Extra 额外信息,如using index、filesort等
select_type
SIMPLE/ PRIMARY/UNION/UNION RESULT/SUBQUERY/DEPENDENT SUBQUERY/
type 访问方法
system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,unique_subquery,index_subquery,range,index,ALL
system:(MyISAM 1)精确的1条记录
const: 根据主键或者唯一二级索引列与常数进行等值匹配
eq_ref: 在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
ref: 当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
index_merge: 使用Intersection、Union、Sort-Union这三种索引合并的方式来执行查询
unique_subquery:
index_subquery: index_subquery与unique_subquery类似,只不过访问子查询中的表时使用的是普通的索引
range: 使用索引获取某些范围区间的记录
index: 查询出的数据在索引内,而查询条件却不能走索引 (二级索引的记录只包含索引列和主键的值, 聚簇索引包含全部列和一些隐藏列)
all:全表查询
性能逐渐降低
key_len
索引长度 varchar(100) 3*100+2(变长字段)+1(可为null字段)
ref
rows
查询记录数
Extra
Using index : 当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下
Using index condition:有些搜索条件中虽然出现了索引列,但却不能使用到索引
Using where
Using join buffer (Block Nested Loop):在连接查询执行过程过,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法
Using temporary: 去重排序groupby等 建立临时表
导致索引失效的查询:
1)查询的数量是大表的大部分,大约30%以上。
2)索引本身失效
3)查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等) 错误的例子:select * from test where id-1=9; 正确的例子:select * from test where id=10;
4)对小表查询
5)隐式转换导致索引失效. 错误的例子:select * from test where tu_mdn=13333333333; 正确的例子:select * from test where tu_mdn='13333333333';
6)like "%_" 百分号在前.
7) 向右匹配直到遇到范围查询(>、<、between、like),后面的索引就会停止匹配
8)单独引用复合索引里非第一位置的索引列.
9)not in ,not exist.
10)B-tree索引 is null不会走,is not null会走
11)联合索引 is not null 只要在建立的索引列(不分先后)都会走, in null时,必须要和建立索引第一列一起使用,当建立索引第一位置条件是is null 时,其他建立索引的列可以是is null(但必须在所有列,都满足is null的时候),或者=一个值; 当建立索引的第一位置是=一个值时,其他索引列可以是任何情况(包括is null =一个值),以上两种情况索引都会走。其他情况不会走。
order by 字段值相同时, 排序可能会随机, 建议再按id 排下序
mysql 连接
mysql -h localhost -u root -p123456
InnoDB页: 将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中
记录
记录头信息
delete_mask 标记当前记录是否被删除, 1表示被删除, 没真正被删除,只是加了标记,并记录在垃圾链表中,为可重用空间,插入数据直接覆盖掉
min_rec_mask 非叶子节点
n_owned 当前记录拥有的记录数
heap_no 当前记录在本页中的位置
record_type
next_record 下一条记录的偏移量 指针指向
Page Directory(页目录)
1.将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组
2. 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
3. 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是所谓的Page Directory,也就是页目录(此时应该返回头看看页面各个部分的图)。页面目录中的这些地址偏移量被称为槽(英文名:Slot),所以这个页面目录就是由槽组成的
n_owned 当前目录保存的记录数
通过二分查找主键所在槽
记录在页中的存储
Page Header(页面头部): 56字节
PAGE_N_DIR_SLOTS 槽的个数
PAGE_HEAP_TOP 从该地址之后就是Free Space
PAGE_N_HEAP 记录数量
PAGE_FREE 垃圾链表的的第一个地址
PAGE_GARBAGE 已删除记录占用字节数
PAGE_LAST_INSERT 最后记录插入位置
PAGE_N_RECS 除去最小最大和删除的记录数
Page Header(文件头部): 38字节
FIL_PAGE_OFFSET 页号
FIL_PAGE_PREV 上一页页号
FIL_PAGE_NEXT 下一页
FIL_PAGE_LSN
FIL_PAGE_TYPE 页的类型 这里是数据页FIL_PAGE_INDEX 0x45BF
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 页所属表空间
写数据太慢, 以页为单位把数据加载到内存中处理, 同步一半时断电,为了检测一个页是否完整 加了一个File Trailer(8个字节)
前4个字节代表页的校验和, 和File Header中的校验和相对应的, 数据被修改后校验和变化,同步到磁盘后,才会把校验和写回File Tailer
后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
B+树索引
recordType: 0 普通用户记录 1 目录项记录 2 最小记录 3 最大记录
简单索引
再把目录项放到数据页中
聚簇索引
以主键为索引, 数据库会自动创建
二级索引
叶子节点保存的是
索引列的值
主键值
页号
查询完后要重新用主键再通过聚簇索引查下 (回表)
主要数据不在二级索引的数据结构中
联合索引 (order by c1, c2)
叶子节点保存的是 (索引列的值 所有主键值)
内节点(目录项记录)(索引列的头值)
一个页面最少存储2条记录
MyISAM的索引
InnoDB中的索引即数据,数据即索引,
而MyISAM中却是索引是索引、数据是数据(索引根节点存的是 主键值+数据行号) 相当于全部都是二级索引
自建索引存的是 相应的列 + 行号
MyISAM
B+树索引的使用
idx_name_birthday_phone_number
使用范围查找
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow' AND birthday > '1980-01-01';
如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引
1.通过条件name > 'Asa' AND name < 'Barlow'来对name进行范围,查找的结果可能有多条name值不同的记录,
2.对这些name值不同的记录继续通过birthday > '1980-01-01'条件继续过滤。
下面这个会用到name-birthday索引
SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday > '1980-01-01' AND birthday < '2000-12-31'
排序 用到索引
asc desc 混用
where 条件不包含索引项
关于回表
使用二级索引后 查询聚簇索引
二级索引 查询出大量数据假如90%, 去查聚簇索引补充全部信息(如果查询需要的值索引包含就不需要再回表了), 不如直接查聚簇索引
查询优化器会进行优化对比数量
我们很不鼓励用*号作为查询列表
如何挑选索引
- 只为用于搜索、排序或分组的列创建索引
- 考虑列的基数(某一列中不重复数据的个数) 最好为那些列的基数大的列建立索引 数据分散
- 索引列的类型尽量小 数据类型越小,在查询时进行的比较操作越快 索引占用的存储空间就越少 数据页内就可以放下更多的记录 把更多的数据页缓存在内存中
- 索引字符串值的前缀 只对name列的前10个字符进行索引name(10) 内存占用少, 比较时间短
- 让索引列在比较表达式中单独出现 不要包含对索引的计算
- 让主键具有AUTO_INCREMENT,让存储引擎自己为表生成主键,而不是我们手动插入
MySQL 的数据目录
SHOW VARIABLES LIKE 'datadir'; // 查看数据路径
InnoDB 表空间
页面类型
FIL_PAGE_TYPE_ALLOCATED 最新分配,还没使用
FIL_PAGE_UNDO_LOG Undo日志页
FIL_PAGE_INODE 段信息节点
FIL_PAGE_IBUF_FREE_LIST Insert Buffer空闲列表
FIL_PAGE_IBUF_BITMAP Insert Buffer位图
FIL_PAGE_TYPE_SYS 系统页
FIL_PAGE_TYPE_TRX_SYS 事务系统数据
FIL_PAGE_TYPE_FSP_HDR 表空间头部信息
FIL_PAGE_INDEX 索引页
区
创建区的理由: 相邻的页物理位置如果很远 就会进行随机IO,性能低, 所以一个区64 个页
区的分类
FREE 空闲的区
FREE_FRAG 有剩余空间的碎片区:表示碎片区中还有可用的页面
FULL_FRAG 没有剩余空间的碎片区
FSEG 附属于某个段的区
为了管理这些区
XDES Entry
Page State Bitmap(16字节)128位 一个区64页 2 位对应一个页 , 表示当前页是否是空闲的
最后怎么放数据的过程:
1.当段中数据过少, 就去找FREE_FRAG的区或者申请FREE
怎么查找这些区呢
FREE 的区 会用ListNode 关联成 FREE链表 FREE_FRAG链表
每个索引对应2个段 每个段对应三个 区链表
链表基节点 (通过他去找链表)
链表对应的List Base Node结构放置在表空间中固定的位置
段(segment)的概念
叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
段的作用:查询扫描时屏蔽叶子节点
一个段 对应多个区
在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间。
各类型页面
FSP_HDR
File SpaceHeader
XDES 类似FSP_HDR(多了表空间的属性)
INODE类型页
这个结构中记录了关于这个段的相关属性
Segment Header 结构的运用
index 页中
PAGE_BTR_SEG_LEAF记录着叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量,PAGE_BTR_SEG_TOP记录着非叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量
名称 占用字节数 描述
Space ID of the INODE Entry 4 INODE Entry结构所在的表空间ID
Page Number of the INODE Entry 4 INODE Entry结构所在的页面页号
Byte Offset of the INODE Ent 2 INODE Entry结构在该页面中的偏移量
系统表空间
InnoDB数据字典
为了更好的管理我们这些用户数据 特意定义了一些列的内部系统表(internal system table)来记录这些这些元数据
表名 | 描述 |
SYS_TABLES | 整个InnoDB存储引擎中所有的表的信息 |
SYS_COLUMNS |
|
SYS_INDEXES | 索引 |
SYS_FIELDS | 所有的索引对应的列的信息 |
SYS_FOREIGN |
|
SYS_TABLESPACES |
|
SYS_DATAFILES |
|
等
系统数据会用类型为sys 的页来存储
访问索引的类型
const ref(多条记录) range all
index(叶子节点包含查询值的索引 但并不是联合索引idx_key_part最左索引列, 走二级索引会快点)
单表访问会将不走索引的条件暂时为true, 然后进行 索引筛选回表后再做筛选
索引合并 在联合索引中的每个列都必须等值匹配 主键列可以是范围匹配
Intersection索引
为甚么必须是等值匹配呢
1.先说下好处, 等值匹配 然后查出的数据 索引列相等 主键Id 排序, 两个数据集合并简单, 拿最小的比较 O(n)
2. 如果取出的是 索引列不等的范围集,就要先把结果集中的主键值排序完再来做上边的那个过程 耗时
IndexA 和IndexB 查出数据后取交集 再回表查询
Union合并
Sort-Union合并 (可以不等值匹配)
SELECT * FROM single_table WHERE key1 < 'a' OR key3 > 'z'
先根据key1 < 'a'条件从idx_key1二级索引总获取记录,并按照记录的主键值进行排序
再根据key3 > 'z'条件从idx_key3二级索引总获取记录,并按照记录的主键值进行排序
因为上述的两个二级索引主键值都是排好序的,剩下的操作和Union索引合并方式就一样了
为啥有Sort-Union索引合并,就没有Sort-Intersection索引合并么
Sort-Union的适用场景是 记录较少
Intersection索引合并 记录数太多,导致回表开销太大 排序 可能大于回表操作
基于块的嵌套连接(Block Nested-Loop Join)算法
先把若干条驱动表结果集中的记录装在这个join buffer中,然后开始扫描被驱动表
只有查询列表中的列和过滤条件中的列才会被放到join buffer中
基于成本的优化
单表查询的成本
SHOW TABLE STATUS LIKE 'single_table'\G
Rows 记录数 Data_length 聚簇索引占用的存储空间大小
全表扫描成本:
I/O 成本 页数 * 1.0+1.1
CPU成本 记录数*0.2+1.0
计算某个索引性能 例如 index > 10 and index < 1000
就会取查找这个索引值最小的id和最大的id
递归根据目录项记录 计算两个也之间的距离
读取二级索引记录cpu 成本: 记录数0.2+0.01
回表操作IO成本: 记录数1.0
读取并检测这些完整的用户记录是否符合其余的搜索条件的CPU成本:记录数*0.2
select * from user where name in ("aa", "bb")
区段计算
先获取索引对应的B+树的区间最左记录和区间最右记录,然后再计算这两条记录之间有多少记录
SHOW VARIABLES LIKE '%dive%';
SHOW INDEX FROM single_table;
Cardinality 索引列中不重复值的数量。
IN语句中的参数个数大于或等于系统变量eq_range_index_dive_limit的值的话(200),就不会使用index dive的方式计算各个单点区间对应的索引记录条数,而是使用索引统计数据
估算 重复记录数 = 总记录数/Cardinality
查询的记录数 = 重复记录数* in 条件里面的个数
连接查询的成本
InnoDB统计数据是如何收集的
CREATE TABLE 表名 (...) Engine=InnoDB, STATS_PERSISTENT = (1|0);
ALTER TABLE 表名 Engine=InnoDB, STATS_PERSISTENT = (1|0);
当STATS_PERSISTENT=1时,表明我们想把该表的统计数据永久的存储到磁盘上,当STATS_PERSISTENT=0时,表明我们想把该表的统计数据临时的存储到内存中。如果我们在创建表时未指定STATS_PERSISTENT属性,那默认采用系统变量innodb_stats_persistent的值作为该属性的值。
mysql> SHOW TABLES FROM mysql LIKE 'innodb%';
+---------------------------+
| Tables_in_mysql (innodb%) |
+---------------------------+
| innodb_index_stats |
| innodb_table_stats |
+---------------------------+
2 rows in set (0.01 sec)
IN子查询优化
otpimizer(优化) trace(跟踪)表的优化
SHOW VARIABLES LIKE 'optimizer_trace'; 查看是否开启
# 1. 打开optimizer trace功能 (默认情况下它是关闭的):
SET optimizer_trace="enabled=on";
# 2. 这里输入你自己的查询语句
SELECT ...;
# 3. 从OPTIMIZER_TRACE表中查看上一个查询的优化过程
SELECT * FROM information_schema.OPTIMIZER_TRACE;
# 4. 可能你还要观察其他语句执行的优化过程,重复上边的第2、3步
...
# 5. 当你停止查看语句的优化过程时,把optimizer trace功能关闭
SET optimizer_trace="enabled=off";
优化过程大致分为了三个阶段:
prepare阶段
optimize阶段
execute阶段
我们所说的基于成本的优化主要集中在optimize阶段,对于单表查询来说,我们主要关注optimize阶段的"rows_estimation"这个过程,这个过程深入分析了对单表查询的各种执行方案的成本;对于多表连接查询来说,我们更多需要关注"considered_execution_plans"这个过程,这个过程里会写明各种不同的连接方式所对应的成本
redo日志 (重做日志):记录对数据的操作
我们只在内存的Buffer Pool中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了
解决的问题:刷新一个完整的数据页太浪费了 随机IO刷起来比较慢(多个不在同一位置的页)
优点: redo日志占用的空间非常小 redo日志是顺序写入磁盘的
redo日志格式
redo日志本质上只是记录了一下事务对数据库做了哪些修改
简单的redo日志
复杂点的类型
redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。
悲观插入(索引改变之类), 会产生多条redo日志 必须保证原子性, 分组 在该组中的最后一条redo日志后边加上一条特殊类型的redo日志,该类型名称为MLOG_MULTI_REC_END
redo log block
redo 日志文件
redo 日志刷盘时机
Log Buffer
Log Sequeue Number
8704
flushed_to_disk_lsn
将log_buffer 中的数据刷入到磁盘中
初始的时候lsn 记录 写入logbiuffer 的偏移量, 后面flushed_to_disk_lsn记录了写如redo日志(磁盘)的偏移量
flush链表中的lsn
一个mtr代表一次对底层页面的原子访问,在访问过程中可能会产生一组不可分割的redo日志,在mtr结束时,会把这一组redo日志写入到log buffer中。除此之外,在mtr结束时还有一件非常重要的事情要做,就是把在mtr执行过程中可能修改过的页面加入到Buffer Pool的flush链表
flush链表中的脏页是按照页面的第一次修改时间从大到小进行排序的
mtr 对页进行修改时 会将页对应的控制块加入到flush链表中, 并将lsn 刷入(o_m), 结束后, 将新的lsn放到尾部(n_m)
checkpoint
undo 日志
InnoDB 的 Buffer pool
Buffer Pool
free 链表管理
缓存页的Hash处理
表空间号+页号 Hash表中 定位缓存是否含有此页记录
flush 链表的管理
修改了BUfferPool上的数据,和磁盘不一致,脏页要刷盘
具体参看上面的flush链表
LRU链表的管理
划分区域的LRU链表
为什么划分:预读优化(顺序预读,随机预读, 如果预读的内容未用到,就会造成劣币驱逐良币)
全表扫描, 更惨,缓存存满了
分为 young(热点数据) 和 old区域(可被清除) 新加入的数据都在old 频繁访问次数间隔大于 innodb_old_blocks_time 会被加入 young
进一步优化, 每次访问不必移到头部, 位置在young1/4 的后边, 才会前移
innodb_buffer_pool_chunk_size
磁盘太慢,用内存作为缓存很有必要。
Buffer Pool本质上是InnoDB向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size来调整它的大小。
Buffer Pool向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后,Buffer Pool剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为碎片。
InnoDB使用了许多链表来管理Buffer Pool。
free链表中每一个节点都代表一个空闲的缓存页,在将磁盘中的页加载到Buffer Pool时,会从free链表中寻找空闲的缓Pool没有可用的空闲缓存页时,会首先淘汰掉old区域的一些页。存页。
为了快速定位某个页是否被加载到Buffer Pool,使用表空间号 + 页号作为key,缓存页作为value,建立哈希表。
在Buffer Pool中被修改的页称为脏页,脏页并不是立即刷新,而是被加入到flush链表中,待之后的某个时刻同步到磁盘上。
LRU链表分为young和old两个区域,可以通过innodb_old_blocks_pct来调节old区域所占的比例。首次从磁盘上加载到Buffer Pool的页会被放到old区域的头部,在innodb_old_blocks_time间隔时间内访问该页不会把它移动到young区域头部。在Buffer
我们可以通过指定innodb_buffer_pool_instances来控制Buffer Pool实例的个数,每个Buffer Pool实例中都有各自独立的链表,互不干扰。
自MySQL 5.7.5版本之后,可以在服务器运行过程中调整Buffer Pool大小。每个Buffer Pool实例由若干个chunk组成,每个chunk的大小可以在服务器启动时通过启动参数调整。
可以用下边的命令查看Buffer Pool的状态信息:
SHOW ENGINE INNODB STATUS
show processlist; 查看mysql进程
https://www.infoq.cn/article/DGMlqL9x0maeHGRltOKT?utm_source=tuicool&utm_medium=referral