为什么分区?
页要连续,顺序IO读写快,每个区都维护一个链表,代表空闲区的指针
FREE
链表:同一个段中,所有页都是空闲的区对应的XDES Entry
结构会被加入到这个链表。注意和直属于表空间的FREE
链表区别开了,此处的FREE
链表是附属于某个段的。NOT_FULL
链表:同一个段中,仍有空闲空间的区对应的XDES Entry
结构会被加入到这个链表。FULL
链表:同一个段中,已经没有空闲空间的区对应的XDES Entry
结构会被加入到这个链表。为什么分段?
区分叶子节点和非叶子节点
页?
稀疏索引,分组
在 MySQL 中定义联合索引时,需要注意以下几点:
列顺序:联合索引中的列顺序非常重要,它会影响索引的效果。通常情况下,应该将被频繁用于查询的列放在联合索引的前面,这样可以使索引在查询时更加有效。例如,如果查询中经常同时使用列 A 和列 B 进行查询,那么联合索引应该按照 (A, B) 的顺序定义,而不是 (B, A)。
列选择:不要将过多的列包含在联合索引中,因为索引的大小对数据库性能有影响。过大的索引可能会导致磁盘 I/O 操作增加,并且会占用额外的存储空间,导致性能下降
索引选择:不是所有的列都适合联合索引。联合索引在使用时会按照索引列的顺序进行匹配,因此只有查询中包含了索引的前缀列时,索引才能被充分利用。因此,应该选择那些在查询中经常一起使用的列进行联合索引定义。
更新性能:联合索引在更新操作时可能会导致性能下降。因为联合索引涉及多个列,更新其中的一个列可能需要更新整个索引。因此,在定义联合索引时应该考虑更新性能的影响,避免频繁的更新操作导致性能下降。
InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
数据库隔离级别是指在多用户并发访问数据库时,数据库管理系统 (DBMS) 为了保护数据的一致性、隔离性和并发性,采取的一系列措施和规定。MySQL 支持以下四种隔离级别:
MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种数据库管理系统(DBMS)中的并发控制机制,用于在多用户并发访问数据库时保障事务的隔离性和一致性。MVCC通过在数据库中为每个事务创建多个版本的数据副本,使多个事务可以同时读取和修改数据库中的数据,而不会相互干扰。
在MVCC模型中,每个事务在读取和修改数据时,都会看到数据库中的一个一致性的快照(snapshot),而不是实际的物理数据。每个事务都有自己的事务ID(Transaction ID),数据库中的每条记录也会保存多个版本,每个版本都包含了一个事务ID和一个时间戳(Timestamp)。当事务执行读取操作时,只会看到在其事务开始之前提交的数据版本,而不会看到在其事务开始之后提交的数据版本。这样可以避免脏读、不可重复读和幻读等并发访问问题。
当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。
记录锁,锁住单行数据
间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的
next-key lock 是包含间隙锁+记录锁的,如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的
一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。
如果有的话,插入操作就会发生阻塞,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个插入意向锁,表明有事务想在某个区间插入新记录,但是现在处于等待状态。
唯一索引等值查询:
非唯一索引等值查询:
非唯一索引和主键索引的范围查询的加锁规则不同之处在于:
其实理解 MySQL 为什么要这样加锁,主要要以避免幻读角度去分析,这样就很容易理解这些加锁的规则了。
InnoDB和MyISAM是MySQL数据库中常见的两种存储引擎,它们有一些重要的区别。
使用like%xxx%
进行模糊查询时,字段的索引就会失效。因此,在数据量大的情况下,通过此种方式查询的效率极低。这个时候,就可通过全文索引(Full-Text Search)来进行优化。
全文索引(Full-Text Search)是将存储于数据库中的整本书或整篇文章中的任意信息查找出来的技术。它可以根据需要获得全文中有关章、节、段、句、词等信息,也可以进行各种统计和分析。
全文索引一般是通过倒排索引实现的,倒排索引如同 B+Tree 一样,也是一种索引结构。它在辅助表中存储了单词与单词自身在一个或多个文档中所在位置之间的映射,这通常利用关联数组实现,拥有两种表现形式:
inverted file index:{单词,单词所在文档的id}
full inverted index:{单词,(单词所在文档的id,再具体文档中的位置)}
创建索引之后可以在索引中查找与关键词匹配的文档列表。可以使用布尔检索、向量空间模型或其他检索算法来计算文档与关键词的相关性,并返回匹配度高的文档结果。
binlog 文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如 SELECT 和 SHOW 操作。
binlog日志可以用于在主从数据库之间复制数据,从而实现数据的高可用和负载均衡等功能。 MySQL的binlog日志有三种格式,分别是Statement格式、Row格式和Mixed格式。它们之间的区别如下:
MySQL 的主从复制依赖于 binlog ,也就是记录 MySQL 上的所有变化并以二进制形式保存在磁盘上。复制的过程就是将 binlog 中的数据从主库传输到从库上。
这个过程一般是异步的,也就是主库上执行事务操作的线程不会等待复制 binlog 的线程同步完成。
MySQL 集群的主从复制过程梳理成 3 个阶段:
具体详细过程如下:
在完成主从复制之后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。
从库是不是越多越好?
不是的。
因为从库数量增加,从库连接上来的 I/O 线程也比较多,主库也要创建同样多的 log dump 线程来处理复制的请求,对主库资源消耗比较高,同时还受限于主库的网络带宽。
所以在实际使用中,一个主库一般跟 2~3 个从库(1 套数据库,1 主 2 从 1 备主),这就是一主多从的 MySQL 集群结构。
MySQL 主从复制还有哪些模型?
主要有三种:
undo log(回滚日志),它保证了事务的 ACID 特性中的原子性(Atomicity)。
每当 InnoDB 引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到 undo log 里,比如:
一条记录的每一次更新操作产生的 undo log 格式都有一个 roll_pointer 指针和一个 trx_id 事务id:
版本链如下图:
因此,undo log 两大作用:
Innodb 存储引擎缓冲池(Buffer Pool)
有了 Buffer Poo 后:
InnoDB 会把存储的数据划分为若干个「页」,以页作为磁盘和内存交互的基本单位,一个页的默认大小为 16KB。因此,Buffer Pool 同样需要按「页」来划分。
在 MySQL 启动的时候,InnoDB 会为 Buffer Pool 申请一片连续的内存空间,然后按照默认的16KB
的大小划分出一个个的页, Buffer Pool 中的页就叫做缓存页。此时这些缓存页都是空闲的,之后随着程序的运行,才会有磁盘上的页被缓存到 Buffer Pool 中。
所以,MySQL 刚启动的时候,你会观察到使用的虚拟内存空间很大,而使用到的物理内存空间却很小,这是因为只有这些虚拟内存被访问后,操作系统才会触发缺页中断,申请物理内存,接着将虚拟地址和物理地址建立映射关系。
Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 Undo 页,插入缓存、自适应哈希索引、锁信息等等。
Undo 页是记录什么?
开启事务后,InnoDB 层更新记录前,首先要记录相应的 undo log,如果是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面。
查询一条记录,就只需要缓冲一条记录吗?
不是的。
当我们查询一条记录时,InnoDB 是会把整个页的数据加载到 Buffer Pool 中,将页加载到 Buffer Pool 后,再通过页里的「页目录」去定位到某条具体的记录。
为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。
后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里,这就是 WAL (Write-Ahead Logging)技术。
WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
过程如下图:
什么是 redo log?
redo log 是物理日志,记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。
在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。
当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。
被修改 Undo 页面,需要记录对应 redo log 吗?
需要的。
开启事务后,InnoDB 层更新记录前,首先要记录相应的 undo log,如果是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面。
不过,在内存修改该 Undo 页面后,需要记录对应的 redo log。
redo log 和 undo log 区别在哪?
这两种日志是属于 InnoDB 存储引擎的日志,它们的区别在于:
事务提交之前发生了崩溃,重启后会通过 undo log 回滚事务,事务提交之后发生了崩溃,重启后会通过 redo log 恢复事务,如下图:
所以有了 redo log,再通过 WAL 技术,InnoDB 就可以保证即使数据库发生异常重启,之前已提交的记录都不会丢失,这个能力称为 crash-safe(崩溃恢复)。可以看出来, redo log 保证了事务四大特性中的持久性。
redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?
写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写。
磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。
针对「顺序写」为什么比「随机写」更快这个问题,可以比喻为你有一个本子,按照顺序一页一页写肯定比写一个字都要找到对应页写快得多。
可以说这是 WAL 技术的另外一个优点:MySQL 的写操作从磁盘的「随机写」变成了「顺序写」,提升语句的执行性能。这是因为 MySQL 的写操作并不是立刻更新到磁盘上,而是先记录在日志上,然后在合适的时间再更新到磁盘上 。
至此, 针对为什么需要 redo log 这个问题我们有两个答案:
产生的 redo log 是直接写入磁盘的吗?
不是的。
实际上, 执行一个事务的过程中,产生的 redo log 也不是直接写入磁盘的,因为这样会产生大量的 I/O 操作,而且磁盘的运行速度远慢于内存。
所以,redo log 也有自己的缓存—— redo log buffer,每当产生一条 redo log 时,会先写入到 redo log buffer,后续在持久化到磁盘如下图:
redo log buffer 默认大小 16 MB,可以通过 innodb_log_Buffer_size
参数动态的调整大小,增大它的大小可以让 MySQL 处理「大事务」是不必写入磁盘,进而提升写 IO 性能。
缓存在 redo log buffer 里的 redo log 还是在内存中,它什么时候刷新到磁盘?
主要有下面几个时机:
binlog和redolog都是用于MySQL数据库的日志。它们都可以用于数据恢复,但是它们的使用场景和恢复方法有所不同。
binlog是MySQL的二进制日志,它记录了所有对MySQL数据库的修改操作,包括插入、更新和删除等。binlog可以用于恢复MySQL数据库到指定的时间点或者指定的事务。具体来说,可以使用mysqlbinlog命令将binlog文件解析成SQL语句,然后再执行这些SQL语句,从而恢复MySQL数据库的状态。
redolog是MySQL的重做日志,它记录了所有对MySQL数据库的修改操作,但是只记录了物理操作,比如页的修改。redolog可以用于恢复MySQL数据库的崩溃恢复,即在MySQL崩溃后,通过重做日志,将数据库恢复到最近一次提交的状态。具体来说,可以使用innodb_recovery命令来进行崩溃恢复,该命令会根据重做日志来恢复数据库。
因此,binlog和redolog都可以用于数据恢复,但是它们的使用场景和恢复方法有所不同。binlog主要用于数据恢复到指定时间点或者指定事务,而redolog主要用于MySQL的崩溃恢复。
首先开启慢查询日志,由参数slow_query_log
决定是否开启,在MySQL命令行下输入下面的命令:
set global slow_query_log=on;
或者可以使用druid连接池去监控
其次设置一个慢查询阈值
set global long_query_time=1;
只要你的SQL实际执行时间超过了这个阈值,就会被记录到慢查询日志里面。这个阈值默认是10s,线上业务一般建议把long_query_time
设置为1s,如果某个业务的MySQL要求比较高的QPS,可设置慢查询为0.1s。
最后通过
show global variables like 'slow_query_log_file'
语句可以找到慢查询的日志
在MySQL中,CHAR
和VARCHAR
都是用于存储文本数据的数据类型,它们之间有以下区别:
CHAR
类型是固定长度的,当存储数据时,会使用固定的空间。例如,如果定义一个CHAR(10)
类型的字段,无论实际存储的数据长度是多少,都会占用10个字符的空间。而VARCHAR
类型是可变长度的,只会占用实际存储的数据长度加上额外的存储空间(1或2个字节用于记录数据长度),对于较短的数据,它占用的空间会更小。CHAR
类型是固定长度的,所以在存储和检索时效率相对较高。而VARCHAR
类型由于长度可变,可能会造成额外的存储空间和内存开销,所以在存储和检索大量数据时,效率可能稍低。CHAR
类型,如果实际存储的数据长度小于定义的长度,会使用空格进行填充以达到指定长度。而VARCHAR
类型不会进行填充。CHAR
类型的列,可以更好地利用索引和排序的性能,因为它的长度固定,查询时可以更快地定位数据。而VARCHAR
类型由于长度可变,可能会影响索引和排序的效率。最左前缀原则:要求建立索引的一个列都不能缺失,否则会出现索引失效
索引列上的计算,函数、类型转换(列类型是字符串在条件中需要使用引号,否则不走索引)、均会导致索引失效:,索引的数据结构只能对原值做索引
索引列中使用 is not null 会导致索引列失效:在where 语句种筛选 idx is null 时,由于索引字段不为空,所以该条件失效,无法查询;在where 语句种筛选 idx is not null 时,由于索引字段本身不为空,所以该条件也失效,会造成全表扫描;
索引列中使用 like 查询的前以 % 开头会导致索引列失效。
索引列用 or 连接时会导致索引失效:如果条件中有or,只要其中一个条件没有索引,其他字段有索引也不会使用。
磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表。
网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库。
SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算。
单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表。
关系型数据库多以B+树类型的索引,在数据量超过阈值的情况,索引深度的增加使得磁盘访问的IO次数增加,导致性能下降,同时,高并发访问使得集中数据库成为系统瓶颈。
MySQL中存储索引用到的数据结构是B+树,B+树的查询时间跟树的高度有关,是log(n),如果用hash存储,那么查询时间是O(1)。
采用Hash来存储确实要更快,但是采用B+树来存储索引的原因主要有以下两点:
一、从内存角度上说,数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次性装入内存,B+树的设计可以允许数据分批加载。
二、从业务场景上说,如果只选择一个数据那确实是hash更快,但是数据库中经常会选中多条,这时候由于B+树索引有序,并且又有链表相连,它的查询效率比hash就快很多了。
B+树是B树的一种变形形式,一颗B+树包含根节点、内部节点和叶子节点,B+树上的叶子节点存储关键字以及相应记录的地址,叶子节点以上各层作为索引使用。
一棵m阶的B+树定义如下:
下面我们通过一个简单的示例说明B+树的详细构建过程:
我们以构建一个5阶B+树为例,根据B+树特性,最少包含2个关键字,最多可以包含4个关键字,当不满足上面条件的时候,可能就需要进行分裂操作了。
最常见的分页写法就是使用limit,在分页查询时,会在 LIMIT 后面传两个参数,一个是偏移量(offset),一个是获取的条数(limit)。
实现方式是先查询offset+limit条数据,再将offset条数据丢弃给用户返回剩下的limit条数据。比如limit 10000,10实际上是mysql查找到前10010条数据,之后丢弃前面的10000行后再返回
这样子当偏移量很小时,查询速度很快,但是随着 offset 变大时,查询速度会越来越慢,因为查找的数据越来越多
select * from t1 where id >= 300000 order by id limit 10
避免了扫描前offset条记录
但是每次查询都需要拿到上一页的最大/小id。比如当前在第3页,需要查询第5页的数据就没办法了
结合普通limit与方式一,解决方式二的问题,但是offset要尽量小
select * from t1 where id > 300000 order by id limit 10, 10
select * from t1 where id > (select id from t1 order by id limit 300000, 1) limit 10
MySQL中决定使不使用某个索引执行查询的依据就是成本够不够小,如果null值很多,还是会用到索引的。
在sql server中,唯一索引字段不能出现多个null值 在mysql 的innodb引擎中,是允许在唯一索引的字段中出现多个null值的。 根据NULL的定义,NULL表示的是未知,因此两个NULL比较的结果既不相等,也不不等,结果仍然是未知。根据这个定义,多个NULL值的存在应该不违反唯一约束,所以是合理的,在oracel也是如此。
会引起歧义,就不允许
假设
一页是16K,页又一些File Header (38 byte)、Page Header (56 Byte)、Infimum + Supermum(26 byte)、File Trailer(8byte), 再加上页目录,大概 1k 左右。 剩下 15k 用于存数据,主键我们假设是 Bigint (8 byte), 而页号也是固定的(4Byte)那么索引页中的一条数据也就是 12byte。
所以非叶子节点内指向其他页的数量为 x , x=15*1024/12≈1280 行。
叶子节点和非叶子节点的结构是一样的,同理,能放数据的空间也是 15k。
但是叶子节点中存放的是真正的行数据,这个影响的因素就会多很多,比如,字段的类型,字段的数量。每行数据占用空间越大,页中所放的行数量就会越少。
这边我们暂时按一条行数据 1k 来算,那一页就能存下 15 条,Y = 15*1024/1000 ≈15。
算到这边了,是不是心里已经有谱了啊。
根据上述的公式,Total =x^(z-1) *y,已知 x=1280,y=15: