MySQL

MySQL

基础知识

MySQL 服务状态命令

  • 启动:systemctl start mysqld
  • 关闭:systemctl stop mysqld
  • 重启:systemctl restart mysqld
  • 查看状态:systemctl status mysqld

字符集

  • 查看字符集

    show variables like 'character%'
    
  • uft8:1~3个字节表示字符

  • utf8mb4:1~4个字节表示字符

比较规则

后缀英文描述
_aiaccent insensitive不区分重音
_asaccent sensitive区分重音
_cicase insensitive不区分大小写
_cscase sensitive区分大小写
_binbinary以二进制方式比较

MySQL 主要文件目录

  • 数据库文件存放目录:/var/lib/mysql
  • 相关命令记录:user/binuser/sbin
  • 配置文件目录:/etc/my.cnf
  • redo日志:/var/lib/mysql/ib_logfile0

表在数据库文件目录中的表示

#创建数据库
CREATE DATABASE `bank01` 
CHARACTER SET 'utf8mb4' 
COLLATE 'utf8mb4_0900_ai_ci';

创建数据库以后会创建文件夹:/var/lib/mysql/bank01

  • InnoDB存储引擎下创建表

    CREATE TABLE `user01` (
        `id` int NOT NULL AUTO_INCREMENT,
        PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    

    创建表后会出现 ibd 文件:/var/lib/mysql/bank01/user01.ibd

    索引、数据、表结构、数据库结构都存储在该文件中

  • MyISAM存储引擎下创建表

    CREATE TABLE `user02` (
      `id` int NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    

    创建表后会出现 .MYD 和 .MYI 文件

    /var/lib/mysql/bank01/user02.MYD:数据信息文件

    /var/lib/mysql/bank01/user02.MYI:索引信息文件

    /var/lib/mysql/bank01/user02_361.sdi:表结构文件

在修改存储引擎以后 文件会重新按照存储引擎的规则建立 之前的文件会删除

在删除表以后 文件会跟着删除

存储引擎

  • 查看存储引擎:show engines;

  • 查看系统默认存储引擎:show variables like '%storage_engine%';SELECT @@default_storage_engine;

  • 修改默认存储引擎:SET DEFAULT_STORAGE_ENGINE=MyISAM;

  • 在 my.cnf 中修改存储引擎:default-storage-engine=MyISAM

  • InnoDB 和 MyISAM 存储引擎对比

    对比项MyISAMInnoDB
    外键不支持支持
    事务不支持支持
    行表锁表锁行锁
    缓存只缓存索引 不缓存真实数据不仅缓存索引还要缓存真实数据 对内存要求较高

逻辑架构

  • 连接层

    客户端访问 MySQL 服务 需要创建 TCP 连接

    MySQL 服务会对 TCP 连接传输过来的账号密码做身份认证 和 权限认证

    MySQL 服务需要从线程池中提供一个线程专门与这个客户端交互

  • 服务层

    • SQL 接口:接收客户端的SQL请求 并返回结果给客户端

    • SQL 解析器:对客户端的SQL进行 词法、语义分析 将SQL分解成语法树 进行语法优化 SQL重写

    • SQL 优化器:生成一个执行计划 用于表明使用哪些索引 表之间的连接顺序 存储引擎会按照执行计划来执行真正的查询

    • 查询缓存:用于缓存一条 select 的执行结果 在8.0版本中删除

      命中率不高:key 必须完全一致 某些函数两次调用结果不一样

      缓存失效:只要表结果或者数据被修改 那么缓存都会变为无效状态

  • 引擎层

    插件式存储引擎层 真正负责 MySQL 中数据的存储提取 对物理服务器级别维护的底层数据执行操作

    服务器通过 API 和存储引擎进行通信 不同的存储引擎具有的功能不同

  • 存储层:文件系统

索引

索引:帮助 MySQL 高效获取数据的数据结构

优点:

  1. 降低数据库 I/O 成本:数据不规则 读取数据的时候磁盘的摆臂需要前后摆动查询数据 数据按照一定顺序摆放的话 可以减少和磁盘的交互数
  2. 保证数据的唯一性:创建唯一索引的情况下
  3. 加速表和表之间的连接:小结果集(驱动表)驱动大结果集(被驱动表)在驱动表创建索引
  4. 提高分组和排序查询的效率 降低 CPU 的消耗

缺点:

  1. 创建和维护索引需要耗费时间 随着数据量的增加 维护时间也会增加
  2. 索引需要额外占用磁盘空间
  3. 降低表的更新速度:索引需要根据插入和更新的数据进行动态维护

InnoBD 的 B+ 树索引

在这里插入图片描述

record_type:表示记录的类型 2-最小记录 3-最大记录 1-目录页记录

next_record:记录下一行数据相对于本行数据的偏移量

为什么要创建目录页:

查询某一项数据需要变量所有数据页 如果要快速定位 需要对每页的数据建立一个目录 即

下一页中的数据必须大于上一页中的数据 把每一页最小的数据向上提 生成一个目录页

  • 聚簇索引
    • 页内的数据按照主键从小到大排序 形成一个单项链表
    • 页与页之间通过双向链表连接
    • 叶子结点存储的是完整的用户数据(所有列包含隐藏列)
  • 非聚簇索引(二级索引)
    • 叶子结点存储的是要索引列和主键值
    • 如果要查询的值在索引列和主键列以外 需要进行回表
    • 如果要查询的值就是索引列和主键列 称为覆盖索引
  • 联合索引
    • 同时将表中的多个列作为索引列 本质上也是一个二级索引
    • 叶子结点包含多个索引列和主键列
  • 注意事项
    1. 根页面万年不动 每次重新生成一个页 会将老页面的数据复制过去并清空
    2. 叶子节点的数据保持唯一性 所以二级索引的叶子结点会存储主键列
    3. 一个页面最少存储两条记录

MyISAM 的索引

在这里插入图片描述

  • MyISAM 的索引文件和数据文件是分离的 索引文件仅保存数据记录的地址
  • MyISAM 的回表是十分迅速的 直接拿着偏移量到数据文件里读取数据

B树 和 B+ 树

B树:平衡多叉树。左边的节点小于自己的值 右边的节点大于自己的值。

在这里插入图片描述

非叶子结点既保存索引也保存数据记录

创建索引

  • 创建表的时候创建索引

    • 创建普通索引
    • 创建唯一索引
    • 创建主键索引
    • 创建联合索引
  • 在已经存在的表上创建索引

    • ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]

      [index_name] (col_name[length],…) [ASC | DESC]

    • CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name

      ON table_name (col_name[length],…) [ASC | DESC]

删除索引

ALTER TABLE table_name DROP INDEX index_name;

DROP INDEX index_name ON table_name;

8.0索引新特性

  • 支持降序索引
  • 支持隐藏索引

InnoDB 数据存储结构

数据页

  • 文件头:

    • 4个字节存储页号

    • 2个字节存储页类型

    • 4个字节存储指向上一页的指针

    • 4个字节存储指向下一页的指针

    • 4个字节存储校验和:为了检查一个数据页是否完整

      当一个页在内存中修改了 在同步之前需要把校验和算出来

    • 8个字节存储页面最后被修改时对应的日志序列位置

  • 文件尾

    • 4个字节存储校验和

      会和文件头里的校验和进行比较

    • 4个字节存储最后被修改时对应的日志序列位置

  • 空闲空间:存储的记录会按照指定的行格式存储到空闲空间

  • 用户记录:各个数据之间形成单链表

  • 最大最小记录:这两条记录不是用户定义的记录

  • 页目录:记录是以单项链表的方式进行存储的 查询效率不高 专门为记录做了一个目录 通过二分法进行检索提高效率

    • 将所有的记录分成几个组 这几个组包含最大记录和最小记录 但不包括已删除的记录
    • 第一组:最小记录所在的组
    • 最后一组:最大记录所在的组
    • 其余组:记录在4~8条之间
  • 页面头部:存储的记录的状态信息 本页第一条记录的地址、本页存储了多少条记录等

行格式

  • MySQL 默认行格式:SELECT @@innodb_default_row_format; dynamic
Compact 格式

一条完整的记录 被分为:记录的真实数据 + 记录的额外信息

  • 变长字段长度列表:2个字节。针对一些变长的数据类型 如varchar/text/blob 存储的字节数是不固定的 所以我们存储的时候需要把这些数据占用的字节数存储起来 存储的变长长度和字段顺序是反过来的
  • Null 值列表:1个字节。把值可以为 Null 的列统一管理起来 如果表中没有存储 Null 的列 那么这个列表也不存在
  • 记录头信息
    • delete_mask:删除标记 所有要被删除的记录会被组成垃圾链表 方便重用空间
    • min_rec_mask:非叶子结点的最小记录都会添加这个标记值为1
    • record_type:2-最小记录 3-最大记录 1-非叶子结点 0-普通记录(叶子结点)
    • heap_no:当前记录在本页中的位置 从2开始 0 和 1分别对应最小记录和最大记录
    • o_owned:页目录中每个组的最后一条记录存储该组中有多少条记录
    • next_record:从当前数据到下一条数据的偏移量
  • 记录的真实数据:包含三个隐藏列
    • DB_ROW_ID:如果一个表没有主键或者唯一索引 则会按照 row_id 创建聚簇索引
    • DB_TRX_ID:事务ID
    • DB_ROLL_PTR:回滚指针
Dynamic 格式

行溢出:InnoDB 存储引擎可以将一条记录中的某些数据存储在真正的数据页面之外

在 Dynamic 下会把占用存储空间非常大的列 分散存储在其它页中 进行分页存储 并在记录真实数据处用 20个字节存储指向这些页的地址

Explain分析工具的使用

列名描述
id在一个大的查询中每一个 select 都对应一个唯一的id 从id大的开始执行
select_typeselect 关键字对应的查询类型
table表名
partitions匹配的分区信息
type针对单表的访问方法
possible_keys可以用到的索引
key实际用到的索引
key_len实际用到的索引长度
ref当使用索引列等值查询时 与索引列进行等值匹配的对象信息
rows预估需要读取的记录条数
filtered经过搜索条件过滤后剩余记录条数的百分比
Extra额外信息

id:如果id相同则认为是一组 从上向下执行 id值越大 优先级越高

select_type:查询类型。SIMPLE、PRIMARY、UNION、SUBQUERY等

type:结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge >

unique_subquery > index_subquery > range > index > ALL

key_len:长度计算公式 utf8=3 + 变长=2 + Null值=1 即varchar(1) 索引长度 = 6

Extra:Using where、Using index、Using join buffer、Using filesort

索引优化与查询

  • 索引失效案例

    • 全值匹配我最爱
    • 最佳左前缀原则
    • 计算、函数、类型转换(自动或手动)导致索引失效
    • 范围条件的右边的索引列失效
    • 不等于索引失效(is not null 无法使用索引)
    • 以 % 开头的查询索引失效
    • or 前后存在非索引列导致索引失效
  • join 语句原理

    • Index Nested-Loop Join(索引嵌套循环)

      1. 从表 t1 中读取一条数据 R
      2. 从数据 R 中取出字段 A 到表 t2 中查找
      3. 取出表 t2 中满足的行和 R 组成一组作为结果集的一部分
      4. 重复1~3 直到表 t1 的末尾

      对 t1 表进行了全表扫描

      对 t2 表进行了索引扫描

    • Simple Nested-Loop Join(简单嵌套循环)

      即对 t2 表没有建立索引 此时 t1 和 t2 表都是全表扫描

    • Block Nested-Loop Join(块嵌套循环)

      添加 join_buffer 减少了 I/O 次数 一块一块的扫描 不再是逐条获取

  • 排序优化

    where 避免全表扫描

    order by 避免 FileSort

    • filesort 算法:
      • 双路排序(慢):两次扫描磁盘 最终得到数据。先从磁盘读取 order by 的列进行排序。然后再从磁盘读取其它字段。
      • 单路排序(快):一次扫描磁盘。从磁盘读取所有列。会使用更多的内存空间。
  • 分页优化

    • 在索引上完成分页操作 然后根据关联的主键回原表查询需要的字段
    • 在自增的索引列上 把limit 换成范围查询
  • 索引下推(index condition pushdown)

    首先找到满足的索引记录区间 然后在索引上直接进行过滤 将过滤后的索引记录进行回表。

    如果不使用索引下推 会找到满足的索引记录后直接回表 然后对回表的记录进行过滤。

数据库的设计范式

范式:数据表设计的基本原则、规则。

  • 第一范式:表中的每个字段必须具有原子性
  • 第二范式:所有非主键字段必须完全依赖主键
  • 第三范式:所有非主键字段必须直接依赖主键 不能通过其它字段间接依赖主键

消除了数据冗余 但是增加了查询成本 需要关联多张表

事务

  • 事务:一组逻辑操作单元 是数据从一种状态变换成另一种状态

  • 事务的特性:(ACID)

    • atomicity:原子性。事务是一个不可分割的工作单位。要么一起成功要么一起失败。(undo日志)
    • consistency:一致性。执行事务前后 数据从一个合法的状态变成另一个合法的状态 这种状态是语义上的。(undo日志)
    • isolation:隔离性。一个事务执行时不能被其它事务干扰。(锁机制)
    • durability:持久性。数据库中的数据的改变是永久性的。(redo日志)
  • 事务的隔离级别:

    • 脏写:事务A提交了修改 事务B也提交了修改 事务B将事务A的提交覆盖了
    • 脏读:事务A修改后没有提交 事务B读取了事务A修改的数据 此时事务A回滚了 读取的数据临时且无效
    • 不可重复读:事务A第一次读取的字段和第二次读取的字段不一致 因为事务B在事务A进行读取的前后修改了字段并提交
    • 幻读:事务A读取表中的某个范围 事务B对表中进行插入操作 事务A再读取这个范围发现多了数据
  • SQL 中的隔离级别:

    数据库的所有隔离级别都能解决脏写问题

    • 读未提交:所有事务都能看到其它事务没有提交的数据。不能避免脏读、不可重复读、幻读。
    • 读已提交:一个事务能看到其它事务已经提交的数据。不能避免不可重复读、幻读。
    • 可重复读:事务A在读到一条数据后 在当前事务中读到的还是原来的数据。(MySQL 的默认隔离级别)
    • 串行化:在一个事务执行的期间 禁止其它事务对该表进行操作。

事务日志

redo日志
为什么需要 redo 日志

缓冲池可以帮助我们消除 CPU 和磁盘之间的鸿沟 checkpoint 机制可以保证数据的最终落盘

然而 checkpoint 并不是每次变更的时候就触发 而是 master 线程隔一段时间去处理

最坏的情况是事务提交以后 刚写完缓冲池 数据库宕机了 那么这段数据就是丢失的 无法恢复

如何保证这部分数据的持久性呢?

将修改的数据记录一下 下次数据库重启的时候 能将这部分数据恢复出来

  • redo 日志的刷盘策略

    InnoDB 引擎会在写 redo_log 的时候先写 redo_log_buffer 之后以一定的频率刷入到真正的 redo_log 文件中 这里的一定频率可以通过 show variables like '%innodb_flush_log_at_trx_commit%';进行查看并修改:

    • 设置为0:表示每次事务提交的时候不进行刷盘操作(后台线程每隔一秒将 redo_log_buffer 中的内容写入 page_cache)
    • 设置为1:表示每次事务提交时都进行刷盘操作(默认值)
    • 设置为2:表示每次事务提交都只把 redo_log_buffer 中的内容写入 page_cache 由操作系统决定何时同步到磁盘。
undo日志
  • 保证了数据的原子性和一致性
  • 多版本并发控制提供支持

在这里插入图片描述

多版本并发控制

MVCC(Multiversion Concurrency Control):通过数据行的多个版本来实现数据库的并发控制。保证 InnoDB 引擎在一致性读的情况下有了保证。

每一个行中包含三个隐藏字段 其中

事务id:每一个事务对某条聚簇索引记录进行修改时 会把该事务id 复制给该列

回滚指针:每次对聚簇索引记录进行改动时 都会把旧的记录写 undo 日志中 然后这列就相当于一个指针 通过它找到该记录修改前的信息

  • ReadView

    可读视图 相当于一个快照 主要用作判断数据的可见性

    ReadView 包含四个比较重要的内容:

    1. creator_trx_id:创建 ReadView 的事务id(select 语句的事务id为0)
    2. trx_ids:表示当前生成 ReadView 时系统中活跃的事务id列表
    3. up_limit_id:活跃事务中id最小的一个
    4. low_limit_id:系统应该分配给的下一个事务id
  • ReadView 的规则

    • 如果访问版本的 trx_id 属性值(行隐藏字段)与 ReadView 中的 creator_trx_id 值相同 意味着当前事务在访问它自己修改的记录 所以该版本可以被当前事务访问
    • 如果访问版本的 trx_id 属性值(行隐藏字段)小于 up_limit_id 表示是生成 ReadView 之前的事务 所以该版本可以被当前事务访问
    • 如果访问版本的 trx_id 属性值(行隐藏字段)大于等于 low_limit_id 表示是生成 ReadView 之后的事务 所以该版本不可以被当前事务访问
    • 如果访问版本的 trx_id 属性值(行隐藏字段)在 up_limit_id 和 low_limit_id 之间 需要判断一下 trx_id 是否在 trx_ids中:
      • 如果在 说明生成 ReadView 时 该版本的事务还是活跃的 所以不能访问
      • 如果不在 说明生成 ReadView 时 该版本的事务已经提交 所以可以访问
  • 读已提交隔离级别下的 MVCC:每次读取数据都会重新生成一个 ReadView

  • 可重复读隔离级别下的 MVCC:只有第一次读取数据才会生成 ReadView

共享锁:针对同一份数据 多个事务可以同时进行操作 互相不阻塞

排它锁:当前锁没有释放的时候 会阻断其它锁的进入

表锁

  • 表级别的 共享锁 和 排它锁
  • 意向锁
    • 实现了表锁和行锁的共存
    • 意向锁由存储引擎自己维护
  • 自增锁:AUTO_INCREMENT 属性
  • 元数据锁:DML 操作时

行锁

  • 记录锁:仅仅把一条记录锁上
  • 间隙锁:某个区间的记录会被锁上 防止幻读的出现
  • 临键锁:既想锁住某条记录 也想锁住间隙的记录
  • 插入意向锁:一个事务在 插入 一条记录时需要判断一下插入位置是不是被别的事务加了间隙锁

主从复制

bin_log

  • bin_log:二进制日志文件 记录了数据库所有执行的 DML 和 DDL 等数据库更新事件语句 但不包含没有修改任何数据的语句(select、show等)
  • bin_log日志主要用于数据恢复和数据复制
查看默认情况
show variables like '%log_bin%';

+---------------------------------+-----------------------------+
| Variable_name                   | Value                       |
+---------------------------------+-----------------------------+
| log_bin                         | ON                          |
| log_bin_basename                | /var/lib/mysql/binlog       |
| log_bin_index                   | /var/lib/mysql/binlog.index |
| log_bin_trust_function_creators | OFF                         |
| log_bin_use_v1_row_events       | OFF                         |
| sql_log_bin                     | ON                          |
+---------------------------------+-----------------------------+
查看日志

当 MySQL 创建二进制文件的时候 会先创建一个 log_bin_index 文件 在创建一个以 “.000001” 为后缀的文件

MySQL 每重启一次 文件的后缀就会增加一个 如果日志长度超过了 max_binlog_size的上限 会创建一个新的日志文件

在shell 命令行执行:mysqlbinlog -v “/var/lib/mysql/binlog.000001”

也可以在 mysql 命令行执行 查看偏移量

show binlog events in ‘binlog.000001’;

  • 使用日志恢复数据

    mysqlbinlog [option] filename|mysql –u user -p pass;

    filename:文件名

    option:

    ​ --start-position和–stop-position:指定恢复的开始位置和结束位置

    ​ --start-date 和 --stop-date:指定恢复的开始时间和结束时间

二阶段提交

在执行更新语句的时候 redo_log 会在事务执行的过程中不断写入 而 bin_log 只会在事务提交的时候写入

两份日志的写入时机不一致

如果redo_log 和 bin_log 两份日志之间的逻辑不一致 会出现什么?

当redo_log 写入正常 在写入 bin_log 的时候 MySQL 程序异常 InnoDB提供了 两阶段提交方案。

  1. 开始事务 更新数据
  2. 写入 redo_log 的准备阶段
  3. 提交事务 同时写入 bin_log 然后redo_log 设置提交阶段

如果在 bin_log 发生异常 发现 redo_log 没有提交 则回滚

如果redo_log 提交发生异常 虽然redo_log 处于准备阶段 但是bin_log 是完整的会进行恢复

relay_log 中继日志

中继日志只存在于主从服务架构的从服务器上。

从服务器为了与主服务器保持一致 要从主服务器上读取 bin_log 日志 并把读取到的信息 写入本地的 relay_log

然后从服务器读取中继日志的内容对从服务器的数据进行更新

在这里插入图片描述

如何解决一致性问题?

主从同步的内容是二进制文件 进行网络传输就一定会存在延迟

Master 等到组内大多数 Slave 更新完毕后再返回 达成最终一致性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值