SQL必知必会(三)-进阶篇

一,数据页和缓冲池

1.数据页

1.1数据页的结构

MySQL 的 InnoDB 存储引擎中,默认页的大小是 16KB

在这里插入图片描述
我们可以把这 7 个数据页分成 3 个部分。

  1. 首先是文件通用部分,也就是文件头和文件尾。它们类似集装箱,将页的内容进行封装,通过文件头和文件尾校验的方式来确保页的传输是完整的。
    文件头中有两个字段,分别是 FIL_PAGE_PREV 和 FIL_PAGE_NEXT,它们的作用相当于指针,分别指向上一个数据页和下一个数据页。连接起来的页相当于一个双向的链表。
    文件尾的校验方式就是采用 Hash 算法进行校验,文件尾的校验和(checksum 值)与文件头的校验和做比对,如果两个值不相等则证明页的传输有问题,需要重新进行传输,否则认为页的传输已经完成
  2. 第二个部分是记录部分,页的主要作用是存储记录,所以“最小和最大记录”和“用户记录”部分占了页结构的主要空间
  3. 第三部分是索引部分,这部分重点指的是页目录,它起到了记录的索引作用,因为在页中,记录是以单向链表的形式进行存储的。单向链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索,因此在页目录中提供了二分查找的方式,用来提高记录的检索效率。

1.2数据页中怎么样查找记录

在这里插入图片描述
如果通过 B+ 树的索引查询行记录,首先是从 B+ 树的根开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止,将数据页加载到内存中,页目录中的槽(slot)采用二分查找的方式先找到一个粗略的记录分组,然后再在分组中通过链表遍历的方式查找记录。
在数据页中:

  1. 将所有的记录分成几个组,这些记录包括最小记录和最大记录,但不包括标记为“已删除”的记录。
  2. 第 1 组,也就是最小记录所在的分组只有 1 个记录;最后一组,就是最大记录所在的分组,会有 1-8 条记录;其余的组记录数量在 4-8 条之间。这样做的好处是,除了第 1 组(最小记录所在组)以外,其余组的记录数会尽量平分。
  3. 在每个组中最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段。
  4. 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录

2.缓冲池

DBMS 会申请占用内存来作为数据缓冲池,这样做的好处是可以让磁盘活动最小化,从而减少与磁盘直接进行 I/O 的时间

缓冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进行读取。

当我们对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新到磁盘上。注意并不是每次发生更新操作,都会立刻进行磁盘回写。缓冲池会采用一种叫做 checkpoint 的机制将数据回写到磁盘上,这样做的好处就是提升了数据库的整体性能
数据页加载的三种方式

  1. 内存读取
  2. 随机读取
  3. 顺序读取

二,数据库中的锁

1.锁划分

  1. 按照锁粒度划分
    行锁、页锁和表锁(Innobo支持行锁和表锁)
    InnoDB 三种行锁的方式
    1. 记录锁:针对单个行记录添加锁。
    2. 间隙锁(Gap Locking):可以帮我们锁住一个范围(索引之间的空隙),但不包括记录本身。采用间隙锁的方式可以防止幻读情况的产生。
    3. Next-Key 锁:帮我们锁住一个范围,同时锁定记录本身,相当于间隙锁 + 记录锁,可以解决幻读的问题。在隔离级别为可重复读时,InnoDB 会采用 Next-Key 锁的机制,帮我们解决幻读问题。
  2. 从数据库管理的角度划分
    共享锁: 又叫S锁,读锁
    LOCK TABLE product_comment READ;(表级)
    SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id = 912178 LOCK IN SHARE MODE(行级)
    排他锁:又叫X锁,写锁,独占锁
    LOCK TABLE product_comment WRITE;(表级)
    SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id = 912178 FOR UPDATE(行级)
    意向锁(Intent Lock):简单来说就是给更大一级别的空间示意里面是否已经上过锁,判断上一级别空间有没有加锁
  3. 从程序员的角度对进行划分
    乐观锁(Optimistic Locking):认为对同一数据的并发操作不会总发生,也就是不采用数据库自身的锁机制,可以采用版本号机制或者时间戳机制实现。
    悲观锁(Pessimistic Locking):共享锁,表锁,行锁,表锁,页锁都是悲观锁
    乐观锁和悲观锁的适用场景
    1. 乐观锁适合读操作多的场景,相对来说写的操作比较少。它的优点在于程序实现,不存在死锁问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作。
    2. 悲观锁适合写操作多的场景,因为写的操作具有排它性。采用悲观锁的方式,可以在数据库层面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突。

2.MVCC机制

1.什么是MVCC机制,有什么用途

MVCC 的英文全称是 Multiversion Concurrency Control,中文翻译过来就是多版本并发控制技术。从名字中也能看出来,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制,简单来说它的思想就是保存数据的历史版本。这样我们就可以通过比较版本号决定数据是否显示出来(具体的规则后面会介绍到),读取数据的时候不需要加锁也可以保证事务的隔离效果。

隔离级别越高,数据的并发能力就越差,MVCC机制可以通过乐观锁的方式来解决不可重复读和幻读问题,降低系统的开销。

通过 MVCC 我们可以解决以下几个问题:

  1. 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,不阻塞读,这样就可以提升事务并发处理能力。
  2. 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
  3. 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

2.InnoDBMVCC机制如何实现的

MVCC相关数据:事务版本号,行记录的隐藏列,undo log
事务版本号:每开启一个事务,我们都会从数据库中获得一个事务 ID(也就是事务版本号),这个事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序
行记录隐藏列:

  1. db_row_id:行ID
  2. db_trx_id:操作这个数据的事务 ID,也就是最后一个对该数据进行插入或更新的事务 ID。
  3. db_roll_ptr:回滚指针,也就是指向这个记录的 Undo Log 信息。
  4. undo log:行记录快照保存在了 Undo Log 里,我们可以在回滚段中找到它们,如下图:
    在这里插入图片描述

2.1ReadView

在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到 Read View 了,它帮我们解决了行的可见性问题。
Read View 保存了当前事务开启时所有活跃(还没有提交)的事务列表,换个角度你可以理解为 Read View 保存了不应该让这个事务看到的其他的事务 ID 列表。

在 Read VIew 中有几个重要的属性

  1. trx_ids ,系统当前正在活跃的事务 ID 集合。
  2. low_limit_id,活跃的事务中最大的事务 ID。
  3. up_limit_id,活跃的事务中最小的事务 ID。
  4. creator_trx_id,创建这个 Read View 的事务 ID。

假设当前有事务 creator_trx_id 想要读取某个行记录,这个行记录的事务 ID 为 trx_id,那么会出现以下几种情况。
1. 如果 trx_id < 活跃的最小事务 ID(up_limit_id),也就是说这个行记录在这些活跃的事务创建之前就已经提交了,那么这个行记录对该事务是可见的。
2. 如果 trx_id > 活跃的最大事务 ID(low_limit_id),这说明该行记录在这些活跃的事务创建之后才创建,那么这个行记录对当前事务不可见。
3. 如果 up_limit_id < trx_id < low_limit_id,说明该行记录所在的事务 trx_id 在目前 creator_trx_id 这个事务创建的时候,可能还处于活跃的状态,因此我们需要在 trx_ids 集合中进行遍历,如果 trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见。否则,如果 trx_id 不存在于 trx_ids 集合中,证明事务 trx_id 已经提交了,该行记录可见。

当查询一条记录的时候,系统如何通过多版本并发控制技术找到它

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 Read View;
  3. 查询得到的数据,然后与 Read View 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。

隔离级别为读已提交(Read Commit)时,一个事务中的每一次 SELECT 查询都会获取一次 Read View

当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的
SELECT 都会复用这个 Read View

MVCC 的核心就是 Undo Log+ Read View,“MV”就是通过 Undo Log 来保存数据的历史版本,实现多版本的管理,“CC”是通过 Read View 来实现管理,通过 Read View 原则来决定数据是否显示。同时针对不同的隔离级别,Read View 的生成策略不同,也就实现了不同的隔离级别。
InnoDB 可以通过 Next-Key 锁 +MVCC 来解决幻读问题。

三,优化器和主从同步

1.查询优化器的两种优化方式

  1. 第一种是基于规则的优化器(RBO,Rule-Based Optimizer),规则就是人们以往的经验,或者是采用已经被证明是有效的方式。通过在优化器里面嵌入规则,来判断 SQL 查询符合哪种规则,就按照相应的规则来制定执行计划,同时采用启发式规则去掉明显不好的存取路径。
  2. 第二种是基于代价的优化器(CBO,Cost-Based Optimizer),这里会根据代价评估模型,计算每条可能的执行计划的代价,也就是 COST,从中选择代价最小的作为执行计划。相比于 RBO 来说,CBO 对数据更敏感,因为它会利用数据表中的统计信息来做判断,针对不同的数据表,查询得到的执行计划可能是不同的,因此制定出来的执行计划也更符合数据表的实际情况。

2.怎么样定位分析SQL执行慢的原因

在这里插入图片描述
某个语句执行慢:
一般查看 慢查询日志定位慢查询语句
然后查看sql语句执行计划
最后修改语句
在这里插入图片描述

3.数据库主从以及备份问题

1.主从同步

从同步设计不仅可以提高数据库的吞吐量,还有以下 3 个方面的作用:

  1. 首先是可以读写分离,减轻数据库压力
  2. 数据备份
  3. 具有高可用性

2.主从同步的原理是怎样的

实际上主从同步的原理就是基于 Binlog 进行数据同步的。在主从复制过程中,会基于 3 个线程来操作,一个主库线程,两个从库线程。

  1. 二进制日志转储线程(Binlog dump thread)是一个主库线程。当从库线程连接的时候,主库可以将二进制日志发送给从库,当主库读取事件的时候,会在 Binlog 上加锁,读取完成之后,再将锁释放掉。
  2. 从库 I/O 线程会连接到主库,向主库发送请求更新 Binlog。这时从库的 I/O 线程就可以读取到主库的二进制日志转储线程发送的 Binlog 更新部分,并且拷贝到本地形成中继日志(Relay log)。
  3. 从库 SQL 线程会读取从库中的中继日志,并且执行日志中的事件,从而将从库中的数据与主库保持同步。
    在这里插入图片描述

四,Redis简单笔记

1.redis基本类型

  1. 哈希:对应的结构是 key-field-value
例:设置值
	hset user1 username zhangfei        
	hset user1 age 28 
	Hmset user1 username zhangfei age 28
	获取值
	hget user1 username
	hmget user1 username age
  1. 列表:底层是双向链表,可以向列表的两端添加元素,有序可重复
往列表加入值
LPUSH heroList zhangfei guanyu liubei
RPUSH heroList dianwei lvbu
获取值
LRANGE key start stop,比如我们想要获取 heroList 从 0 到 4 位置的数据,写成LRANGE heroList 0 4                                                
  1. 字符串集合:无序不可重复
添加元素
 SADD key member [...]
 SADD heroSet zhangfei guanyu liubei dianwei lvbu
删除元素
SREM key member [...]
SREM heroSet liubei lvbu 
获取所有元素
SMEMBERS heroSet
判断集合中是否存在某个元素
SISMEMBER key member
SISMEMBER heroSet zhangfei
  1. 有序集合:ZSET 是在集合的基础上增加了一个分数属性,这个属性在添加修改元素的时候可以被指定。每次指定后,ZSET 都会按照分数来进行自动排序,也就是说我们在给集合 key 添加 member 的时候,可以指定 score。
给有序集合的元素添加分数
ZADD key score member [...]
ZADD heroScore 8341 zhangfei
删除一个或多元素
ZREM key member [member …]
ZREM heroScore zhangfei
获取从小到大的值(WITHSCORES 会将分数一同显示出来)
ZRANGE key start stop [WITHSCORES]
获取从大到小的值(WITHSCORES 会将分数一同显示出来)
ZREVRANGE key start stop [WITHSCORES]
  1. 字符串类型:它是 Redis 提供的最基本的数据类型,对应的结构是 key-value。

除了这 5 种数据类型以外,Redis 还支持位图(Bitmaps)数据结构,在 2.8 版本之后,增加了基数统计(HyperLogLog),3.2 版本之后加入了地理空间(Geospatial)以及索引半径查询的功能,在 5.0 版本引用了数据流(Streams)数据类型。

2.redis优点

  1. redis是基于内存的key-value数据库,使用 Hash 结构进行操作,数据的操作复杂度为 O(1)。
  2. 它采用单进程单线程模型,这样做的好处就是避免了上下文切换和不必要的线程之间引起的资源竞争。
  3. 采用了多路 I/O 复用技术,可以在同一个线程中处理多个 I/O 请求,尽量减少网络 I/O 的消耗,提升使用效率
  4. 有丰富的数据类型,支持的数据类型包括字符串、哈希、列表、集合、有序集合等。

3.redis事务

1.redis事务相关特性

  1. Redis 不支持事务的回滚机制(Rollback)。
  2. 无法保证持久性,不过 Redis 也提供了两种持久化的模式,分别是 RDB 和 AOF 模式。
  3. RDB( Redis DataBase)持久化可以把当前进程的数据生成快照保存到磁盘上,触发 RDB 持久化的方式分为手动触发和自动触发。因为持久化操作与命令操作不是同步进行的,所以无法保证事务的持久性。
  4. AOF(Append Only File)持久化采用日志的形式记录每个写操作,弥补了 RDB 在数据一致性上的不足,但是采用 AOF 模式,就意味着每条执行命令都需要写入文件中,会大大降低 Redis 的访问性能。启用 AOF 模式需要手动开启,有 3 种不同的配置方式,默认为 everysec,也就是每秒钟同步一次。其次还有 always 和 no 模式,分别代表只要有数据发生修改就会写入 AOF 文件,以及由操作系统决定什么时候记录到 AOF 文件中。
  5. Redis 是单线程程序,总是具有隔离性

2. Redis 的事务处理都包括哪些命令:

  1. MULTI:开启一个事务;
  2. EXEC:事务执行,将一次性执行事务内的所有命令;
  3. DISCARD:取消事务;
  4. WATCH:监视一个或多个键,如果事务执行前某个键发生了改动,那么事务也会被打断;
  5. UNWATCH:取消 WATCH 命令对所有键的监视。

猜你喜欢

转载自blog.csdn.net/qq_41201565/article/details/106355494