数据库相关基础

目录

  • ORM框架
    • Hibernate
    • MyBatis
    • MyBatis 与 Hibernate 比较
  • JPA
  • 性能优化与数据库
    • 性能优化关注
    • 性能优化与数据库的关系
  • 关系型数据库
    • 关系型数据库的设计范式(NF:Normal Form)
    • 常见的关系型数据库
    • 非关系型数据库
  • MySQL
  • MySQL数据库原理
  • MySQL Server逻辑框架
  • Server层
    • 连接器(Connection Pool)
      • 第一次权限检查(仅针对用户权限)
      • 空闲连接、长/短连接
      • 连接与内存
    • 查询缓存(Cache&Buffers)
      • 显式指定查询缓存
      • 第二次权限检查
    • 分析器(parser)
      • 第二次权限检查-Precheck
    • 优化器(optimizer)
    • 执行器(SQL Interface)
      • 第三次权限检查
      • 执行语句
  • MySQL的系统自带数据库
  • MySQL 存储
    • 独占模式
    • 共享模式
    • Innodb的文件物理存储结构
  • MySQL执行流程
  • MySQL对SQL的执行顺序
  • MySQL索引
    • 常见的索引类型
    • 聚集索引(聚簇索引)与非聚簇索引
    • 唯一索引与非唯一索引
    • 单表数据上限=2000万左右
  • MySQL常用引擎对比(引擎层)
    • Myisam与Innodb的进一步对比
    • 关于自增Id
    • Innodb的特性
  • MySQL 数据类型
  • MySQL常用的SQL
  • MySQL常见客户端/操作工具
  • MySQL配置优化
  • MySQL的表设计优化
  • SQL优化
  • 深分页查询优化
  • MySQL快速导入导出、备份数据
  • MySQL对事务的支持与锁
    • 表级锁
      • 意向锁
      • 其他表锁
      • 加表锁的情况
    • 行级锁
    • Innodb加锁规律总结
    • 死锁
    • MySQL对不同的事务隔离级别支持
    • 事务隔离级别和锁的相关SQL
  • MySQL常见使用场景优化
    • 实现分布式唯一ID
      • 利用数据库的自增ID
      • 利用sequence(仅限商业数据库)
      • 模拟实现的sequence
      • 使用其他方式
    • 高效分页
    • 更新数据
  • MySQL日志
    • undo log
    • MVCC
    • redo log(Innodb log)
    • redo log buffer
    • redo log持久化
    • redo log 的大小和数量
    • redo log file 存放的位置
    • Write-Ahead Logging(WAL 技术)
    • crash-safe
    • redo log的擦除
      • buffer pool,干净页、脏页
      • 刷脏页的四种场景
      • InnoDB 刷脏页的控制策略
      • 刷脏页需要知道的参数
    • redo log的由来
    • [Innodb log 所有参数 及 数量计算](https://blog.csdn.net/keketrtr/article/details/113861373)
    • bin log
    • MySQL 怎么知道 bin log 是完整的
    • bin log和redo log的差别
    • binlog和redo log的工作流程
      • 两阶段提交
      • 两阶段提交的必要性
      • 如何关联redo log 和 bin log
      • 只有bin log的情况
      • 只有redo log的情况
    • general_log

ORM框架

  • ORM(Object-Relational Mapping) 表示对象关系映射
  • 常见的两种ORM框架:Hibernate(自动化)、MyBatis(半自动化)

Hibernate

  • Hibernate 是一个开源的对象关系映射框架,它对JDBC 进行了非常轻量级的对象封装,它将 POJO 与数据库表建立映射关系,可以针对不同数据库的”方言”自动生成适宜 SQL 语句,自动执行,使得Java 程序员可以使用面向对象的思维来操纵数据库
    Markdown将文本转换为 HTML。
  • Hibernate 里需要定义实体类和 hbm 映射关系文件(Hibernate Bean Maping ,IDE中一般有工具生成)
  • Hibernate 里可以使用 HQL、Criteria、Native SQL三种方式操作数据库,也可以作为 JPA 规范的适配实现,使用 JPA 接口操作(主要的使用方式)
    数据库相关基础_第1张图片

MyBatis

  • MyBatis 是支持定制化 SQL、存储过程以及高级映射
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO映射成数据库中的记录
    数据库相关基础_第2张图片
  • MyBatis-半自动化 ORM原因:需要使用映射文件 mapper.xml 定义 map 规则和 SQL、需要定义mapper/DAO接口,基于 xml 规则,操作数据库
  • MyBatis使用经验:可以使用工具生成基础的 mapper.xml 和 mapper/DAO接口,可以让自定义的mapper接口继承工具生成生成的 mapper接口,而不是覆盖掉,也可以直接在 mapper/DAO 接口上用注解方式配置 SQL

MyBatis 与 Hibernate 比较

  • Mybatis 优点:原生SQL(XML 语法),直观,对 DBA 友好
  • Hibernate 优点:简单场景不用写 SQL(HQL、Cretiria、SQL)
  • Mybatis 缺点:繁琐,可以用 MyBatis-generator、MyBatis-Plus 之类的插件
  • Hibernate 缺点:对DBA 不友好,SQL都是针对不同数据库”方言”自动生成的、字段名字冗长,十分难以阅读

JPA

  • JPA 的全称是 Java Persistence API, 即 Java 持久化 API,是一套基于 ORM 的规范,内部是由一系列的接口和抽象类构成(类似JDBC,但是需要底层的ORM框架去增强实现具体的数据库持久化操作)
    在这里插入图片描述

  • 核心类 EntityManager–实体管理器,提供实体类的DML、DQL方法对应于不同数据库表的DML、DQL操作

  • JPA 通过 JDK 5.0 及以上提供的注解描述对象-关系表映射关系,并将运行期的实体对象持久化到数据库中

  • JDBC与JPA的区别
    1.操作的对象不一样,且使用的技术也不相同,比如JDBC通过封装JDBC接口进而通过数据库连接池等技术操作DataSource进行持久化;JPA通过增强实现JPA的ORM,操作EntityManager进行持久化
    2.项目中引入的jar包不同,前者是Spring JDBC 后者是 Spring ORM
    数据库相关基础_第3张图片

性能优化与数据库

性能优化关注

1.吞吐与延迟 :指导我们关注什么
2.没有量化就没有改进:监控与度量指标,指导我们怎么去入手
3.80/20原则:先优化性能瓶颈问题,关键的少数问题导致了大部分的性能问题,指导我们如何去优化
4.过早的优化是万恶之源:指导我们要选择优化的时机
5.脱离场景谈性能都是耍流氓:指导我们对性能要求要符合实际

性能优化与数据库的关系

  • 大部分的性能问题都出现在三个方面:
    1.SQL问题
    2.数据库表结构问题
    3.数据库自身性能问题
  • 任何系统都无法绕过这三方面的原因
    1.业务处理系统本身无状态,所有数据状态最终要保存到数据库
    2.一般来说,DB/SQL 操作的消耗在 业务系统 的一次请求处理中占比最大,随着时间的发展,系统对数据库三个方面的要求也会变化

关系型数据库

  • 什么是关系型数据库:使用关系模型,以关系代数理论为数学基础,处理和描述数据
  • 关系型数据库可以通过E-R(Entity-Relationship)图表达表与表、表与字段之间的关系
    数据库相关基础_第4张图片

关系型数据库的设计范式(NF:Normal Form)

1.第一范式(1NF):表里面的每一个字段都不可以继续拆分成多个字段
2.第二范式(2NF):表里面的非主键字段不能不依赖于主键字段或者是只依赖于联合主键的部分字段
3.第三范式(3NF):消除传递依赖,非主键字段除了依赖主键,不能再依赖于非主键字段
4.BC 范式(BCNF):主要针对联合主键,联合主键可能导致数据插入不成功,因为关键业务数据缺少联合主键中的部分字段,这说明该表对于改业务而言可以继续拆分为多张表,减少联合主键包含的字段
5.第四范式(4NF):消除非平凡的多值依赖&第五范式(5NF):消除一些不合适的连接依赖

常见的关系型数据库

1.开源:MySQL、PostgreSQL
2.商业:Oracle,DB2,SQL Server
3.内存数据库:Redis(严格算不上数据库因为会造成数据丢失),VoltDB
4.图数据库:Neo4j,Nebula
5.时序数据库:InfluxDB、openTSDB
6.其他关系数据库:Access、Sqlite、H2、Derby、Sybase、Infomix 等
7.NewSQL/分布式数据库:TiDB、CockroachDB、NuoDB、OpenGauss、OB、TDSQL

非关系型数据库

NoSQL 数据库:MongoDB、Hbase、Cassandra、CouchDB

MySQL

  • MySQL数据库现在有两个版本:MariaDB 和 MySQL
  • MySQL数据库发展的版本
    数据库相关基础_第5张图片
  • 使用量最大的三个版本:5.5.xxx、5.6.xxx、5.7.xxx,三者比较相近,无大改动,改动最大的是5.7.xxx升级为8.0.xxx
    数据库相关基础_第6张图片

MySQL数据库原理

  • MySQL 架构:一个MySQL Server对客户端来说就是一整个数据库
    数据库相关基础_第7张图片
  • MySQL的连接池(Connection Pool)是BIO的所以需要避免长连接

MySQL Server逻辑框架

数据库相关基础_第8张图片

  • MySQL Server内又可以划分为 Server层 与 存储引擎层

Server层

Server 层包括连接器、查询缓存、分析器、优化器、执行器,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等

连接器(Connection Pool)

接器负责跟客户端建立连接、获取权限、维持和管理连接
连接命令一般是这么写的:

mysql -h -P -u -p
[mysql ]: 是客户端工具
[-h]: 表示后面跟着输入数据库服务器的地址
[-P]: 表示后面跟着输入访问数据库服务器的端口
[-u]: 表示后面跟着需要输入用户名
[-p]: 表示敲完回车后面跟着输入访问数据库服务器的端口

第一次权限检查(仅针对用户权限)

用户名、密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限,即为一次查询,一直使用,只有再新建的连接才会使用新的权限设置

空闲连接、长/短连接

1、空闲连接:连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,通过show processlist 命令查看所有连接,其中如图所示command列为“sleep”的即为空闲连接
数据库相关基础_第9张图片
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时

2、长连接:指连接成功后,如果客户端持续有请求,则一直使用同一个连接

3、短连接:每次执行完很少的几次查询就断开连接,下次查询再重新建立一个

建立连接的过程通常是比较复杂的,建议使用长连接

连接与内存

MySQL 在执行过程中临时使用的内存是由创建的连接对象管理、划分、使用的,如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM,全称“Out Of Memory”),从现象看就是 MySQL 异常重启了

解决由连接数过多导致的MySQL异常重启:

1、定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存大的查询后,断开连接,之后要查询再重连

2、MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态

查询缓存(Cache&Buffers)

MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果

优点:如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高

缺点:查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空

MySQL 8.0 版本直接将查询缓存的整块功能删掉了

显式指定查询缓存

将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:

Select  SQL_CACHE  *  from  T  where  ID=10

第二次权限检查

如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证,验证的是针对该表的查询权限

分析器(parser)

当查询缓存未命中,则开始执行语句,分析器负责解析SQL语句判断需要做什么,以及检查语法和涉及到的表与字段

第二次权限检查-Precheck

先判断一下你对这个表有没有执行相应操作的权限,如果没有,就会返回没有权限的错误,但是对于存储过程、视图等是无法检查的,必须要在执行器执行前进行检查

优化器(optimizer)

经过了分析器,MySQL 就知道要做什么了,优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序

执行器(SQL Interface)

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句

第三次权限检查

开始执行的时候,要先判断一下你对这个表有没有执行相应操作的权限,如果没有,就会返回没有权限的错误,这是最终的权限检查,主要检查Precheck无法检查的如视图、存储过程等

执行语句

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口

MySQL的系统自带数据库

  • information_schema是 MySQL 系统自带的数据库,主要保存 MySQL 数据库服务器的系统信息,比如数据库的名称、数据表的名称、字段名称、存取权限、数据文件 所在的文件夹和系统使用的文件夹,等等
  • performance_schema是 MySQL 系统自带的数据库,可以用来监控 MySQL 的各类性能指标。
  • sys数据库是 MySQL 系统自带的数据库,主要作用是以一种更容易被理解的方式展示 MySQL 数据库服务器的各类性能指标,帮助系统管理员和开发人员监控 MySQL 的技术性能。
  • mysql数据库保存了 MySQL 数据库服务器运行时需要的系统信息,比如数据文件夹、当前使用的字符集、约束检查信息、用户,等等

MySQL 存储

数据库相关基础_第10张图片

用户创建的数据库所在目录主要看安装时的设置,上图路径时默认路径

  • MySQL 存储分为独占模式和共享模式
  • MySQL 存储默认为独占模式,控制两种模式的切换通过设置innodb_file_per_table=OFF/ON
  • 不管是独占还是共享存储模式,数据文件都存放在MySQL的安装目录中

独占模式

  • 每一个数据库都有各自的如下等相关文件,文件名就是datebase名,按照不同schema(等同于datebase)名的文件区分独立存放当前schema中的table
    1.日志组文件:ib_logfile0 和 ib_logfile1(innodb log=redo log),默认均为5M
    2.表结构文件:.frm
    3.表空间文件:
    .ibd
    4.字符集和排序规则文件:db.opt
    5.bin log 二进制日志文件:记录主数据库服务器的 DDL 和 DML 操作
    6.二进制日志索引文件:master-bin.index

共享模式

数据库中的所有schema中的table相关文件都在 ibdata1、ibdata2…等系统表空间中

Innodb的文件物理存储结构

数据库相关基础_第11张图片

数据库相关基础_第12张图片

  • 数据库对于table的存储总共有5种存储逻辑单位
    • 1.table space表空间
    • 2.segment段
    • 3.extent区
    • 4.page页
    • 5.row行
  • 表空间可以分为
    • 1.系统表空间(system table space)文件
      • 文件以ibdata1、ibdata2等命名,包括元数据数据字典(表、列、索引等)、double write buffer、插入缓冲索引页(change buffer)、系统事务信息(sys_trx)、默认包含undo回滚段(rollback segment)
    • 2.用户表空间文件
      • 存储了 用户创建 的 数据库和表 的 索引和数据,聚簇索引树结构就是数据的存储结构
    • 3.其它,诸如临时表空间文件、undo独立表空间等
  • 关于 table space、segment、extent、page、row的大小参考文章

MySQL执行流程

数据库相关基础_第13张图片

  • 解析树:解析抽象语法树(AST)
    数据库相关基础_第14张图片

MySQL对SQL的执行顺序

1.from、on、join:按照次序主要是为了找到涉及到的Table
2.where、group by、having+聚合函数:按照次序主要是为了对前面找到的所有表的记录进行过滤Filter
3.select、order by、limit:按照次序主要是为了将满足过滤条件的记录在客户端展现Show

MySQL索引

常见的索引类型

  • Hash:和HashMap有一样的问题
    数据库相关基础_第15张图片

  • B-Tree:容易造成树过高,需要多次I/O
    数据库相关基础_第16张图片

  • B+Tree:主键长度不能过大
    数据库相关基础_第17张图片

  • Innodb为什么选择B+树作为数据的存储结构
    1.所有数据都在叶子结点,有助于减少层数,随机IO的次数就更少
    2.叶子结点之间组成双向链表,对于遍历和范围查询效率更高
    3.使用自增主键,能够减少叶分裂导致的计算消耗,提高存储数据的速度

聚集索引(聚簇索引)与非聚簇索引

  • 数据是按页来分块的,一个数据块就是一个数据页(树结点),当一个数据被用到时,其附近的数据也通常会马上被使用,InnoDB 使用 B+ 树实现聚集索引

  • 对于InnoDB 而言聚集索引(聚簇索引)也是主键索引,如下图B+树就是聚簇索引,每一个节点就一个数据块,叶子结点存储了多个主键索引所在行的全部记录信息,并且叶子结点之间有的关系可以看成是双向节点,这样可以方便的存取数据
    数据库相关基础_第18张图片

  • 一张Innodb表只能有一个聚簇索引,聚簇索引叶子结点的数据顺序就是数据的物理存储顺序

  • 非聚簇索引=辅助索引=二级索引=非主键索引=联合索引
    数据库相关基础_第19张图片
    数据库相关基础_第20张图片

  • 主键索引的更新
    替换更新

  • 非主键索引的更新
    删除+新建

唯一索引与非唯一索引

  • 唯一索引包括:主键索引、唯一约束的字段(自动建立唯一索引同时也是二级索引)、唯一的联合索引(二级索引)
  • 非唯一索引包括:除了以上情况的二级索引

单表数据上限=2000万左右

  • 为什么一般单表数据不超过2000万
    索引树一般不会超过三层,因为每次查询数据都会从B+树的根节点开始向下搜寻直至叶子节点,每向下一层或换一个数据页(一个节点就是一个数据页page)就需要读一次磁盘,磁盘I/O对性能影响大
  • 计算方法
    1.MySQL一个数据页一般是16kB,指针一般是6B
    2.假设主键索引是bigint数据类型占用8B,一条记录是1KB
    3.一个数据页能放下16×1024÷(6+8)≈1170个主键指针
    4.三层的B+树能存储的数据为1170²×16≈2100万

MySQL常用引擎对比(引擎层)

数据库相关基础_第21张图片

  • Memory引擎使用的是内存,其大小限制取决于内存的大小,archive引擎使用硬盘,理论上没有大小限制

Myisam与Innodb的进一步对比

数据库相关基础_第22张图片

  • Myisam默认的锁级别是表锁,innodb默认的锁级别是行锁

  • Myisam的存储文件中的索引文件仅存储了 索引+对应记录的存储地址,innodb因为一定会使用聚簇索引,所以数据和索引是一同保存为.ibd的数据文件中的,并且数据的存储结构和聚簇索引相一致
    数据库相关基础_第23张图片

  • 当使用select count(*)查询表中记录数时,Myisam可以直接返回,因为使用该引擎的的表会统计并存储表中记录数,innodb需要遍历主键索引树

innodb由于需要支持事务引入了MVCC,这就导致了不同事务查寻到的记录条数是不同的

  • Myisam可以没有主键,但是innodb必须要有主键,主键来源可以是手动创建,也可以是数据库自动创建。手动创建如自增Id等,当主键的值超过了主键数据类型所能表示的最大值时会报错。如果是数据库自动创建的主键,一般为不会展示出来的长度为6字节的隐藏列row_id作为主键,当主键的值超过了6字节所能表示的最大范围后,会重新从0开始,且之前row_id为0的记录被覆盖

row_id是innoDB中维护的一个全局变量dictsys.row_id,没有定义主键的表都会共享使用这个row_id,在插入数据时会把这个全局row_id当作自己的主键,然后再将这个全局变量加 1

关于自增Id

  • 单库且数据量较小时适合使用自增Id,能够避免页分裂与合并,增加数据页的利用率
  • Myisam引擎的自增值是保存在数据文件中的,innodb在8.0之前,自增值是保存在数据库内存中的,此时数据库重启会发生丢失,再次使用时需要根据表中的max(id)+1作为自增的起始值。innodb在8.0及之后,自增值的变更是保存在redo log中
  • 自增Id不一定是连续的:1.自增的过程不会识别插入语句是否成功,每次执行插入语句都会自行+1、2.删除之前插入过的记录也会导致自增Id不连续、3.事务回滚,但是自增Id不会恢复、4.批量插入时会导致自增Id不连续,因为自增Id在SQL语句执行过程中按照2^(申请自增Id次数-1)的数量分配自增Id,所以前一次事务未能使用完的自增Id,在下一次事务中只能使用以此为基础+1的自增Id、5.自增的步长不为1时,自增Id也是不连续的

Innodb的特性

  • 为了维持性能同时又兼顾可靠性,Innodb存储引擎常用的策略:双写缓冲区、故障恢复、操作系统、fsync() 、磁盘存储、缓存、UPS、网络、备份策略 ……
  1. 使用了insert buffer/change buffer,仅针对DML的条件为非唯一索引且涉及到的记录所在数据页不存在于内存中的情况下使用,操作合并以后减少了IO次数
  2. 使用了double write缓存,该缓存位于系统表空间的存储区域,用于存储 在清理buffer pool刷的脏页数据,因为数据写入到double write缓存的IO消耗要远小于数据写入到磁盘文件中的消耗,所以数据总是先写入到double write缓存,然后会以一次大的连续块的方式从double write缓存写入到磁盘文件中

在应用 redo log 时,需要先从double write缓存中得到一个目标数据页的副本用于还原数据页,然后再重做,这也是double write的意思

  1. 使用了自适应哈希索引(Adaptive Hash index,AHI),innodb引擎对于一些被频繁访问的二级索引数据(最近连续被访问三次及以上的数据),会自动被生成到hash索引中
  • innodb 会监控对表上的二级索引的查询情况,对于满足条件的二级索引数据,如果观察到建立哈希索引后能够带来速度提升,则会自动生成对应的哈希索引,所以被称为自适应哈希索引
  • 自适应哈希索引使用buffer pool构建B+树来构造,所以速度会非常快
  1. innodb 使用线性预读(linear read-ahead)和随机预读(randomread-ahead),两种预读算法提升IO性能
  • 线性预读以extent为单位,用于将下一个extent提前读取到buffer pool中
  • 随机预读以extent中的page为单位,用于将extent中剩余的page提前读取到buffer pool中

MySQL 数据类型

  • 对JSON数据类型的使用
  • 其他数据类型

MySQL常用的SQL

  1. show schemas --显示所有数据库
  2. create schema 'my_Schemas' --创建名为my_Schemas的数据库
  3. use 'my_Schemas'–使用/切换当前数据库为'my_Schemas'
  4. show variables like '%dir%' --查看数据库所有的目录,basedir这一项指向了MySQL的安装目录,datadir这一项指向了MySQL各个数据库文件的存储目录(无论是独占还是共享存储模式)
  5. show variables like '%port%' --查看数据库使用的所有端口
  6. show variables like '%version%'–查看数据库相关所有版本号
  7. create table test_1(id int not null,primary key(id))engine=InnoDB default charset=utf8mb4 --在当前数据库创建表名为 test_1的建表语句
  8. show tables --查看当前数据库所有的表
  9. show create table 'test_1' --查看名为 test_1的表的建表语句
  10. show columns from 'test_1' --查看表名为 test_1的所有列;
  11. 其他命令
  12. explain 这个命令来查看一个这些SQL语句的执行计划,就是为了分析耗时,是否走索引。在Oracle中是explain plan for命令查看索引执行计划
  13. 创建用户、修改用户权限指令
  14. performance_schema 的启用和关闭
  15. 排查阻塞或查看表的锁状态参考文章1、文章2
  16. 查看当前数据库的连接、引擎、表的状态和信息

MySQL常见客户端/操作工具

mysql-cli 或 IDE(DataGrip,MySQL-WorkBench,MySQL-Front,Navicat 等)

MySQL配置优化

  • MySQL配置优化主要针对MySQL的配置文件中配置参数的修改
  • MySQL配置文件一般在MySQL的安装目录下,对于Windows系统,文件名是 my.ini;对于非Windows系统而言,文件名是my.cnf
  • MySQL配置文件包含主要的两项配置,分别是[mysqld] 与 [mysql],一般针对数据库的配置优化主要是修改配置项[mysqld] 下面的配置参数
  • [mysqld]配置参数项下面的参数主要针对于整个SQL server
  • [mysql]配置参数项下面的参数主要针对每个SQL client
  • 查看配置参数
    1.直接打开配置文件文件查看
    2.不知道具体配置参数的变量名时可以通过show variables like '%xxx%' --模糊查询配置变量名包含xxx的所有配置参数
    3.知道参数名的情况下可以直接通过show variables like ‘xxx’ or select @@xxx --查看配置变量名为xxx配置参数
  • 配置参数分为全局配置参数 与 当前会话配置参数,当前会话配置参数一般与全局配置参数的变量名与值相同;有些配置参数只有全局配置参数,这些配置参数的变量名一般都以global开头或包含global单词
  • 查看全局配置参数可以通过 show global variables like '%xxx%' or show glob variables like 'xxx' or select @@xxx
  • 修改配置参数可以通过 set xxx=aaa or set global.xxx=aaa ,有些参数是只读的不能修改
  • 对于全局配置参数的修改,在MySQL8.0之前是不会会持久化的,MySQL重启以后就需要重新设置,这个问题在8.0及以后的版本得到了修复
  • 配置参数优化一般有三个方面
    1.连接请求的配置变量
    2.缓冲区配置变量
    3.配置 Innodb 的几个配置变量
  • 连接请求的配置变量包括
    1.max_connections最大连接数
    2.back_log等待MySQL接受的新连接请求数
    3.wait_timeout等待超时的时间 和 interative_timeout交互状态下的超时时间,对于超时的连接会被断开,早期的连接池不会自动重连和test连接,现在的连接池基本都实现了重连和test连接
  • 缓冲区配置变量包括
    1.key_buffer_size这个参数只针对MyISAM存储引擎,用于设置存储索引的缓存区大小决定了索引操作的速度
    2.query_cache_size(查询缓存简称 QC)这个参数只针对server端的缓存而非存储引擎端的,MySQL8.0及以后的版本已经去掉了
    3.max_connect_errors设置连接建立过程中发生错误的最大连接数
    4.sort_buffer_size设置排序缓存区大小,默认只有1M
    5.max_allowed_packet=32M设置允许向MySQL发送的数据的最大的大小
    6.join_buffer_size=2M设置连接表时用到的连接缓存区大小
    7.thread_cache_size=300设置线程的缓存大小
  • 配置 Innodb 的几个配置变量包括
    1.innodb_buffer_pool_size=128M设置存储 使用索引的查询 的缓存区大小,存储内容同时包括索引和对应的记录
    2.innodb_flush_log_at_trx_commit设置提交事务的时候将 redo 日志写入磁盘中的策
    3.innodb_thread_concurrency=0设置并发线程数
    4.innodb_log_buffer_size设置日志的缓存区大小
    5.innodb_log_file_size=50M设置日志文件的大小
    6.innodb_log_files_in_group=3设置日志组中的日志文件数目,推荐是3
    7.read_buffer_size=1M设置读取数据的时候缓存区大小
    8.read_rnd_buffer_size=16M设置随机读数据的时候缓存区大小
    9.bulk_insert_buffer_size=64M设置批量插入的缓存区大小
    10.binary log 设置bin log相关的参数,有很多

MySQL的表设计优化

  • 选择恰当引擎
    1.不需要事务且数据的操作量比较大使用MyISAM
    2.需要事务等可以使用默认的Innodb
    3.只需要在内存临时建个表且要求速度快、数据量小、不需要持久化使用Memory
    4.当数据都需要归档的使用Archive或tuko(压缩效率高)
  • 库表命名
    不建议使用拼音或缩写
  • 合理拆分宽表
  • 选择恰当数据类型:明确、尽量小
    1.char、varchar 的选择,char是固定为设置长度的,varchar是可变长度的,根据实际的存储的数据长度在0到给定值之间变化,推荐使用varchar
    2.对于比较大的文本数据其实不建议使用text/blob/clob,因为这会导致数据库性能下降,数据块能存储的数据变少了;实在不行也可以先将表的其他字段存储进去,最后通过update的方式将text/blob/clob类型字段的数据更新到对应记录的对应字段
    3.不建议直接将文件、图片存入到数据库,可以存储在服务器的磁盘中或分布式文件系统中,然后数据库存储路径或远程访问的URL
    4.时间日期的存储问题
    5.数值的精度问题,需要注意的是int(3)与int(8)没有任何区别,仅仅只是展示的不同
    6.字段的数据类型长度可以适当冗余,但是过多冗余会造成成本上升
  • 唯一约束和索引的关系
    唯一约束也是一种索引=唯一索引
  • 冗余字段
    可以适当将子表中经常用到的字段添加到主表中作为冗余字段
  • 不建议使用游标、变量、视图、自定义函数、存储过程,因为不同数据库这些内容不相同
  • 自增主键的使用问题
    数据不大的情况下推荐使用自增主键,当数据量很大考虑分布式数据库的场景时比如需要分库、分表就不适用了
  • 不能在线修改表结构(DDL 操作)会造成锁表
    DDL 操作的危害:
    1.导致索引重建
    2.导致锁表
    3.抢占数据库服务器资源
    4.造成主从延时
  • 逻辑删除还是物理删除,推荐逻辑删除
  • 推荐加 create_time,update_time 时间戳
  • 数据库碎片问题,优化该问题时也会造成锁表
  • 不建议使用外键、触发器
  • 设计表之前,通读 DBA 的指导手册/dbaprinciples
  • 考虑数据量随着时间、业务变化的增长
  • 考虑字段随着时间、业务变化的新增与删减

SQL优化

1.隐式的数据转换

  • 使用索引字段作为查询条件时,需要注意数据类型,如果发生数据类型转换,除了会导致没有走索引(索引失效),还可能会导致SQL错误

2.慢查询

  • 慢查询的排查方式
  • 1.通过慢查询日志
    • 慢查询日志(slow log)中的 rows_examined表示一个查询语句执行过程中扫描了多少行,这个值就是在执行器每次调用引擎获取数据行的时候累加的(扫描行数=在索引树上取数据的次数 )
  • 2.通过查看应用和运维的监控

3.索引相关

  • 常见的索引不正确使用情况
  • 1.冗余索引
    • 索引冗余严重时甚至会造成索引占用空间比数据本身占用空间大
    • 冗余索引的情况一般如下
      • 1.长的包括短的,形成冗余,比如(username, name) 与(username)
      • 2.有唯一约束的,组合冗余,比如唯一索引(username)与 (username,id)
  • 2.能使用主键索引却使用二级索引,会造成回表
  • 3.索引的选择性/区分度差
    • 唯一索引的选择性/区分度最好
    • 非唯一索引的选择性/区分度根据重复出现相同的该索引字段值的记录占总记录数的比值衡量,比值越低,该索引的选择性/区分度越好

4.写入优化

  • 通过PreparedStatement 减少 SQL 解析
    • 使用PreparedStatement ,ORM会先将SQL发送给数据库服务器解析,然后每次写入数据都是在对应的占位符填入对应的value,而不是每次写入都要重新解析SQL
  • Multiple Values/Add Batch 减少交互
    • 插入时可以使用insert into xxx values (a,a,a),(b,b,b)...,这种方式就是Multiple Values,或者通过PreparedStatement的Add Batch,JDBC可以通过特定参数实现Multiple Values
  • Load Data,直接导入
    • 通过csv格式的文件,每一列都用逗号隔开,整个导入到数据库中
  • 索引和约束
    • 导入数据前,去掉所有该表的索引和约束,导入数据完毕以后再重新建立索引和约束,这样可以避免导入数据时因为索引不断调整索引树结构导致插入变慢

5.数据更新优化

  • 注意间隙锁、临键锁的问题,比如尽量不要使用范围更新,可以通过in实现

6.模糊查询优化

  • like 不要以通配符开头,利用好最左前缀匹配规则,否则就不会走索引
  • 使用全文检索
    • 1.MySQL里自带的全文检索的方式建立全文检索的倒排序索引,通常用于复杂的多个字段的模糊匹配
    • 2.常用的全文检索技术框架 solr/ES,当需要查询的数据量特别大,且模糊查询复杂,由多个字符型的字段组合作为查询条件,此时就可以使用solr/ElasticSearch

7.连接查询优化

  • 选择的驱动表(主表)要尽量小,以做到数据更明确以及条件匹配越精确
  • 避免笛卡尔积

8.索引失效优化

  • 查询条件不要对索引进行与NULL的比较与运算(对null做算术运算的结果都是null),或者是与NULL有not、not in、!、<>相关的操作且如果有is null,则只能出现一次,最好是直接将数据库字段定义为not null
  • 减少使用 or,可以用 union(注意 union all 的区别)以及 like
    • 1.union与union all:多个查询使用union可以实现去重,使用union all则不会去重
    • 2.减少使用 or:因为or后面的条件与其他精确的条件、索引都没有关系,是并列的,此时可能会导致不走索引
    • 3.使用like时,不能以’%'开头
  • 避免联合索引失效的情况
  • 避免索引发生隐式的类型转换 或 对索引使用函数,比如字符类型索引,查询时不加双引号
  • 索引发生失效:因为索引查询只会比较索引的原值,而在MySQL中一个常数可以对应多个字符串表达式,比如数字类型的1对应字符串’1‘、’1a‘、’ 1 ‘等,以上任意一种情况的发生都不能与原值比较,如果想要使用函数的索引生效,MySQL8.0及以上版本可以使用函数索引

9.大查询SQL优化

  • 平衡查询数据量和查询次数
  • 避免不必须的在SQL Server 和SQL client 重复传输数据
    • 比如查1万条数据,然后取这些数据某个字段的平均值,此时不能先查出1万条数据然后在代码里面做运算,而是应该直接在数据库做运算取最后的结果
  • 避免使用临时文件排序或临时表

深分页查询优化

  • 深分页:比如查询SQL加上limit 1000000 10offset=1000000size=10,表示在最后查出来的结果集中前1000000 条记录抛弃,只取后面10条
  • 深分页的危害:
    查询总是先通过server层的执行器调用存储引擎提供的接口,当这些数据完全符合要求(比如满足其他where条件),则会放到server层的结果集中,最后server层返回给调用mysql的客户端
    1. 如果使用了二级索引并且没有因为优化器而导致全表扫描,意味着多出offset次的回表,且查询的字段越多,意味着单条记录所占内存越大,就浪费了越多MySQL服务器的内存用于存放最终要被舍弃的记录,而且查询速度降低
    2. 如果使用了主键索引并且没有因为优化器而导致全表扫描,虽然不用回表,但是同样存在查询的字段越多,意味着单条记录所占内存越大,就浪费了越多MySQL服务器的内存用于存放最终要被舍弃的记录,也会造成查询速度降低(情况好的话不会太多,但也有性能损耗,有可能造成刷脏页)
  • 深分页优化:
    • 通过前面危害的分析,优化可以从两方面同时进行
      1.减少回表次数
      2.减少无用记录浪费的MySQL服务器内存:原因是分页查询,会先查回所有记录存放到server层内存中,然后再分页
    • 主要的方式有
      1.通过子查询直接定位到第 offset条记录,然后主查询在此基础上再往后取size条记录
      2.通过 高效分页 的手段

MySQL快速导入导出、备份数据

  • 对于导出、备份数据
    一般情况下会造成锁表,如果有主从结构的数据库集群,可以直接通过从库实现,或者通过bin log实现
  • 对于导入
    1.不断地insert,可以通过Java代码、批量insert、脚本等方式
    2.也可以通过load date 方式把一个文本的所有数据装载到MySQL中
    3.最后可以考虑使用数据的迁移工具实现快速导入导出、备份数据

MySQL对事务的支持与锁

  • MySQL的锁
    1.表级锁
    2.行级锁
    3.共享锁(S)、排它锁(X)
    所有的锁都是针对索引字段,S锁和X锁既能是行级锁也能使表级锁

表级锁

意向锁

  • 上锁前会先上意向锁,表明事务稍后要进行哪种类型的锁定,上锁前都要先加意向锁
    1.共享意向锁(IS): 打算在某些行上设置共享锁(S)前会在表上加IS
    2.排他意向锁(IX): 打算对某些行设置排他锁(X)前会在表上加IX
    3.Insert 意向锁: Insert 操作设置的间隙锁
    数据库相关基础_第24张图片
  • 上图锁兼容性都是建立在表级锁的级别,S、X可以是表级锁也可以是行级锁,具体是表级锁还是行级锁需要看SQL是否使用索引以及涉及到的范围
  • S锁是通过SQL手动加的:select * from table_name lock in share mode;
  • X锁可以是SQL手动加:select * from table_name for update;,也可以是自动加,自动加的时候是因为updatedelete(记录锁、临键锁、间隙锁都是排它锁的一种)
  • IX与S冲突的前提是,隔离级别为非RC且IX产生的原因为DML(半一致性读的优化避免了冲突),而非for update

其他表锁

  • 自增锁(AUTO-IN)
    同时对使用自增主键的表插入多条记录时,为了保证自增主键不冲突,此时对该表加上自增锁
  • LOCK TABLES 表锁
    一般发生在dump(比如flush tables with read lock;(FTWRL))、DDL(添加元数据锁(MateDataSource Lock,MDL))时

加表锁的情况

  • DML且没有走索引,此时S、X会成为表级锁(当DML操作的记录存在时)
  • DML时会自动对涉及到的表添加MDL读锁,DDL是会自动对涉及到的表添加DML写锁

行级锁

1.记录锁(Record): 锁定索引对应记录
2.间隙锁(Gap): 锁住一个范围
3.临键锁(Next-Key): 记录锁+间隙锁的组合
4.谓词锁(Predicat): 空间索引

  • 间隙锁:锁住一个区间的索引,该区间是开区间,区间内不再允许新增记录,条件是隔离级别为可重复读
  • 临键锁:锁住一个区间的索引,该区间是前开后闭区间,区间内不再允许新增记录且区间后端对应的记录不允许DML,条件是隔离级别为可重复读,这也是加锁的基本单位

Innodb加锁规律总结

  • 对于搜索(DML都会涉及到搜索)涉及到主键索引、唯一索引,即走到了对应的索引树
    1.等值查询且记录存在
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id = 1 for update;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | 路飞   |  19 |
+----+--------+-----+
1 row in set (0.02 sec)

数据库相关基础_第25张图片

2.等值查询且记录不存在

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id = 2 for update;
Empty set (0.03 sec)

数据库相关基础_第26张图片
3.范围查询

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id > 15 for update;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
| 20 | 香克斯    |  39 |
+----+-----------+-----+
1 row in set (0.01 sec)

数据库相关基础_第27张图片

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id >= 15 for update;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
| 15 | 乌索普    |  20 |
| 20 | 香克斯    |  39 |
+----+-----------+-----+
2 rows in set (0.00 sec)

数据库相关基础_第28张图片

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id < 6 for update;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | 路飞   |  19 |
|  5 | 索隆   |  21 |
+----+--------+-----+
3 rows in set (0.00 sec)

数据库相关基础_第29张图片

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id <= 5 for update;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | 路飞   |  19 |
|  5 | 索隆   |  21 |
+----+--------+-----+
2 rows in set (0.00 sec)

数据库相关基础_第30张图片

select * from user where id < 5 for update;

数据库相关基础_第31张图片

  • 对于搜索(DML都会涉及到搜索)涉及到非唯一索引,即走到了对应的索引树
    1.等值查询且记录存在
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where age = 22 for update;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
| 10 | 山治   |  22 |
+----+--------+-----+
1 row in set (0.00 sec)

数据库相关基础_第32张图片

当查询的记录「存在」时,由于不是唯一索引,所以肯定存在索引值相同的记录,于是非唯一索引等值查询的过程是一个扫描的过程,直到扫描到第一个不符合条件的二级索引记录就停止扫描,然后在扫描的过程中,对扫描到的二级索引记录加的是 next-key 锁,而对于第一个不符合条件的二级索引记录,该二级索引的 next-key 锁会退化成间隙锁。同时,在符合查询条件的记录的主键索引上加记录锁

2.等值查询且记录不存在

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where age = 25 for update;
Empty set (0.00 sec)

数据库相关基础_第33张图片

当查询的记录「不存在」时,扫描到第一条不符合条件的二级索引记录,该二级索引的 next-key 锁会退化成间隙锁。因为不存在满足查询条件的记录,所以不会对主键索引加锁

3.范围查询

非唯一索引和主键索引的范围查询的加锁类似,只是范围不同

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where age >= 22  for update;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
| 10 | 山治      |  22 |
| 20 | 香克斯    |  39 |
+----+-----------+-----+
2 rows in set (0.01 sec)

数据库相关基础_第34张图片
4.关于间隙锁区间端点对应的记录是否允许插入参考文章

二级索引的间隙锁,区间的端点记录是否允许插入同样索引值的记录,还需要看端点记录对应的主键,因为二级索引值相同的情况下,是按照主键值排序的,意味着插入与间隙锁端点记录同样的二级索引值记录有可能因为主键值更小或更大而不在间隙锁的区间内,所以可以插入

死锁

  • 造成死锁的原因
    1.阻塞与互相等待
    2.增删改与锁定读
  • 解决或避免死锁的方法
    • 死锁检测与自动回滚
      1.死锁检测,innodb 提供了 wait-for graph 算法来主动进行死锁检测,每当加锁请求无法立即满足需要并进入等待时,wait-for graph 算法都会被触发,发现死锁后,主动回滚或kill死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑(默认是开启状态)
      2.设置事务最长等待时长,超过这个时长自动回滚,通过参数 innodb_lock_wait_timeout 根据实际业务场景来设置超时时间,InnoDB引擎默认值是50s
    • 锁粒度与程序设计
      注意每一句SQL所造成的的锁的范围,减小锁的范围(锁粒度);程序设计时避免造成相互依赖

MyISAM 中是不会产生死锁的,因为 MyISAM 总是一次性获得所需的全部锁,要么全部满足,要么全部等待

MySQL对不同的事务隔离级别支持

  • 事务的隔离级别
    1.读未提交: READ UNCOMMITTED
    2.读已提交: READ COMMITTED
    3.可重复读: REPEATABLE READ
    4.可串行化: SERIALIZABLE
  • MySQL的默认隔离级别是可重复读(历史原因):可以设置全局的默认隔离级别,也可以单独设置会话的隔离级别;其他数据库的默认隔离级别是读已提交

读未提交

  • 很少使用,以非锁定方式执行,不能保证一致性会产生脏读、幻读、不可重复读

读已提交

  • 1.每次搜索都会设置和读取一次数据库新快照
  • 2.仅支持基于行的 bin log
  • 3.UPDATE 优化: 半一致性读(semi-consistent read)
    • 半一致性读:当前事务可以读取已经加锁(其他事务加的锁)的行,用来判断是否满足本事务的where条件,如果满足,则进入锁定等待,不满足则继续读其他行
    • 一致性读:读到的数据一定是其它事务已经提交以后的,当本事务启动后,数据库中的数据相对于本事务而言相当于是“时停”的

4.会产生:不可重复读、幻读(Phantom)
5.默认情况下只有记录锁

  • 可重复读
    1.是InnoDB 的默认隔离级别
    2.使用事务第一次读取(搜索表数据)即执行第一句SQL时(DMLorDQL)创建快照,后续该事务的诸多SQL都是用同一个数据库快照
    3.使用多版本并发控制技术(MVCC)
    4.使用唯一索引的唯一查询条件时,只锁定查找到的索引记录, 不锁定间隙;其他查询条件,会锁定扫描到的索引范围,通过间隙锁或临键锁来阻止其他会话在这个范围中插入值
    5.可能的问题: InnoDB 不能保证没有幻读,需要加锁
  • 可串行化:最严格的级别,事务串行执行,资源消耗最大

事务隔离级别和锁的相关SQL

  • 查看当前数据库的锁和事务:SHOW ENGINE INNODB STATUS \G;
  • 查看当前事务锁的情况:select * from performance_schema.data_locks;
  • 查看和设置隔离级别SQL:隔离有会话和系统两种级别,show variables like '%isolation%';
  • 查看当前会话隔离级别:select @@tx_isolation;show variables like 'transaction_isolation';
  • 查看系统当前隔离级别:select @@global.tx_isolation;
  • 设置当前会话隔离级别:set session transaction isolatin level 隔离级别;
  • 设置系统当前隔离级别:set global transaction isolation level 隔离级别;

MySQL常见使用场景优化

实现分布式唯一ID

  • 分布式场景下唯一ID的特点:
    1.全局唯一
    2.趋势递增
    3.单调递增
    4.高可用高并发

利用数据库的自增ID

  • 缺点是分库分表时作为唯一ID会重复

利用sequence(仅限商业数据库)

  • 所有数据插入数据库前都可以访问存储sequence的专用表,向其他数据库的表插入一条记录前,先查询sequence表获取唯一的序列号作为主键和唯一ID,这样既可以避免重复,也不用担心分库分表
  • 缺点是金商业数据库支持

模拟实现的sequence

  • 比如MySQL之类的数据库是没有专门的sequence表的,此时可以通过创建仅设立仅包含 序列名称、当前值(初始值)、步长这三个字段的表,作为模拟sequence表的实现
  • 实际使用可以参考sequence表,插入记录到其它数据库的表前可以先排他的查询该模拟sequence的表的对应序列名的行,取出当前值+1作为起始序列号,[起始序列号,起始序列号+步长] 的序列号区间存储到系统内存中,并更新模拟sequence表的当前值为 起始序列号+步长
  • 此时只要查一次就可以在一段时间内为系统提供保证不重复的一组唯一ID
  • 缺点是当系统发上宕机,还没用的序列号就真的再也用不了了

使用其他方式

使用其他方式的优点是避免了前面三种方式造成的唯一ID连续问题,连续的序列号作为唯一ID有可能会泄露业务的数据规模

  1. UUID
    - 以当前的计算机性能,单机产生的UUID几百年都不会重复,所以可以利用UUID产生的字符串序列作为主键
    - 缺点是产生得字符串序列太无序且长
  2. 时间戳/随机数
    - 利用精确到毫秒的时间戳和随机数组合可以产生不会重复的序列号作为主键
    - 缺点是分布式系统还是会出现重复
  3. 雪花算法snowflake
    - 雪花算法可以产生一个类型为long的序列号
    - 该序列号组成:代表当前机器的序列号+时间戳+随机数,不同的机器有不同的序列号,能够避免不同机器生成的序列号可能重复的问题
    数据库相关基础_第35张图片
  4. 使用Leaf算法
    数据库相关基础_第36张图片
    数据库相关基础_第37张图片
    • Leaf核心表
      数据库相关基础_第38张图片
      读取到的号段可以放在Redis集群中
      数据库相关基础_第39张图片
    • 号段的读取和更新问题
      数据库相关基础_第40张图片

高效分页

1.分页插件

  • 使用 mybatis 的分页插件,会在每一句查询SQL前添加select count(*)from(查询SQL),根据结果集的记录数和分页查询条件合理的自动分页
  • 缺点是当遇到大数据量的且会带有连接查询的情况时会变得很慢,如果此时仅需要主表的数据时,会浪费性能,可以通过改写select count(*)from…仅统计主表结果集的记录数

2.其他手段

  • 反序
    • 反序是指业务场景为查询正序的位置倒数的记录时,可以反序查询,比如假设一共10万零20条记录,limit 10,0000 20取10万条记录的后20条记录,此时需要遍历10万条记录后才能最终返回最后的20条记录,如果反序查询就是limit 20 则直接去前20条记录
  • 查询条件带上关键的ID
    • 查询下一页或下n页时可以通过当前页最后一条记录的ID,根据每页的记录数和翻页的数量计算出limit a b 的值
  • 模糊分页
    • 当数据量特别大时,前端可以通过缩略的方式展示,比如当前页为第一页,前端的翻页按钮可以是1…1000,让客户通过点击其中的省略号自由选择跳转到哪一页

更新数据

  • 更新数据有两种加锁方式,可以根据业务需求选择不同的更新方式
    1.悲观锁,默认一定会发生冲突,提前加锁
    数据库相关基础_第41张图片
    2.乐观锁,先尝试更新,更新失败则重试
    数据库相关基础_第42张图片

MySQL日志

undo log

  • undo log即为撤消日志
    1.保证事务的原子性,使得不同事务之间互不影响
    2.用于事务回滚、一致性读、崩溃恢复
    3.记录事务回滚时所需的撤消操作(回滚段rollback segment):一条 INSERT 语句,对应一条 DELETE 的 undo log、每个 UPDATE 语句,对应一条相反 UPDATE 的 undo log
  • undo log保存位置
    1.system tablespace(MySQL 5.7 默认)
    2.undo tablespaces(MySQL 8.0 默认)

MVCC

  • MVCC((Multiversion Concurrency Control)多版本并发控制技术
    1.使 InnoDB 支持一致性读: READ COMMITTED 和 REPEATABLE READ
    2.让查询不被阻塞:无需等待被其他事务持有的锁,这种技术手段可以增加并发性能
    3.为InnoDB 保留被修改行的旧版本
    4.能够实现查询正在被其他事务更新的数据时,会读取更新之前的版本
    5.每行数据都存在一个版本号(隐藏列), 每次更新时都更新该版本号
    6.这种技术在数据库领域的使用并不普遍:某些数据库, 以及某些 MySQL 存储引擎都不支持
  • MVCC 实现机制
    1.在每行数据的后面都添加了隐藏列
    数据库相关基础_第43张图片
    2.事务链表, 保存还未提交的事务,事务提交则会从链表中摘除
    3.快照Read view: 每个 SQL 一个, 包括 rw_trx_ids, low_limit_id, up_limit_id, low_limit_no 等
    4.回滚段: 通过 undo log 动态构建旧版本数据

redo log(Innodb log)

  • redo log 即为重做日志,是 InnoDB 引擎特有的日志
  • redo log 需要关注的内容
    1.确保事务的持久性,防止事务提交后数据未刷新到磁盘就掉电或崩溃—redo log 持久化
    2.事务执行过程中写入 redo log,记录事务对数据页做了哪些修改
    3.提升性能: WAL(Write-Ahead Logging) 技术,先写日志(顺序写),再写磁盘(随机写)
    3.存储日志的文件: ib_logfile0, ib_logfile1…最多四个,每个1GB
    4.日志缓冲: innodb_log_buffer_size
    5.支持强刷: fsync()

数据库相关基础_第44张图片
数据库相关基础_第45张图片
数据库相关基础_第46张图片

redo log buffer

数据库相关基础_第47张图片

  • redo log buffer 就是一块内存,用来先暂存 redo 日志的,在执行第一个 insert 的时候,redo log buffer 也被写入了日志内容(此时容易发生丢失)。但是,真正把日志写到 redo log 文件(文件名是 ib_logfile+ 数字),是在执行 commit 语句(注意与事务实际commit做区分)的时候记录redo log文件(此时不会丢失)
  • redo log buffer的大小默认16MB
    数据库相关基础_第48张图片

redo log持久化

innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘

innodb_flush_log_at_trx_commit={0|1|2} 

指定何时将事务日志刷到磁盘,默认为1

  • 0表示延迟写延迟刷: “log buffer"→ 一秒后 →"os buffer” → 磁盘日志文件
    如果MySQL服务器崩溃,会丢失1s的数据
  • 1表示实时写实时刷:每次事务都 “log buffer"→"os buffer” → 磁盘日志文件
    不会丢失任何数据,但是IO性能较差
  • 2表示实时写延迟刷:每次事务提交都 “log buffer"→"os buffer” → 一秒后 →磁盘日志文件
    innodb_flush_log_at_trx_commit=0的情况

数据库相关基础_第49张图片

数据库相关基础_第50张图片
数据库相关基础_第51张图片

redo log 的大小和数量

  • InnoDB 的 redo log 是由参数innodb_log_file_size决定的,默认是48MB
    在这里插入图片描述
  • Innodb 的 redo log file 的数量是由参数innodb_log_file_in_group决定的数据库相关基础_第52张图片
    redo log是从write pos(当前记录的位置)开始写,一边写一边后移,同时check point 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录 更新到数据文件

数据库相关基础_第53张图片

redo log file 存放的位置

  • Innodb_log_group_home_dir指定了redo log file 的存放位置,默认值为取决于MySQL的存储模式(独占 or 共享)
    在这里插入图片描述

Write-Ahead Logging(WAL 技术)

当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log里面,并更新内存(如果此时涉及到的数据页已经在内存就更新内存,不在内存就不更新内存而是直接记录日志),这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候(系统比较空闲 或 必要的时候),将这个操作记录更新到磁盘里面(刷脏页

crash-safe

保证短时间内最近的更新能够在出现意外后能够恢复的能力

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe
在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页

redo log的擦除

redo log擦除 = flush 脏页

buffer pool,干净页、脏页

InnoDB 用缓冲池(buffer pool)管理内存,内存存在三种状态

  • 还没有使用的,InnoDB 的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的内存很少
  • 使用了并且是存的是干净页,干净页是指从磁盘读入的数据页且没有任何变动,与磁盘上的数据页相同,此为干净页
  • 使用了并且存的是脏页,脏页是指从磁盘读入内存的数据页,在后面有了对该数据页的更新、插入、删除等导致此时内存中的该数据页和磁盘上的对应数据页不相同,此为脏页

刷脏页的四种场景

  • InnoDB 的 redo log 写满了,redo log一般最多设置为4G。这时候系统会停止所有更新操作,把 check point 往前推进,redo log 留出空间可以继续写
    数据库相关基础_第54张图片
    check point 可不是随便往前修改一下位置就可以的。比如上图中,把 check point 位置从 CP 推进到 CP’,就需要将两个点之间的日志(浅绿色部分),对应的所有脏页都 flush 到磁盘上。之后,图中从 write pos 到 CP’之间就是可以再写入的 redo log 的区域
  • 数据库系统内存不足。当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘
  • MySQL 认为系统“空闲”的时候或者合理地安排时间,即使是不空闲的时候,也要见缝插针地找时间,只要有机会就刷一点“脏页”
  • MySQL 正常关闭的情况。这时候,MySQL 会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快

InnoDB 刷脏页的控制策略

  • 一旦一个查询请求需要在执行过程中先 flush 掉一个脏页时,这个查询就可能要比平时慢
  • MySQL中的一个机制,可能让查询会更慢:在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好也是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷

在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的
该功能对于机械硬盘提升比较大,因为机械硬盘IOPS比较低,对于SSD硬盘,IPOS不再是瓶颈,需要关掉,自己刷自己的效率反而更高

刷脏页需要知道的参数

  • InnoDB 所在主机的 IO 能力,需要用到 innodb_io_capacity 这个参数,它会告诉 InnoDB ,主机的磁盘 IO能力。这个值建议设置成磁盘的 IOPS(每秒的读写次数)。磁盘的 IOPS 可以通过 fio 这个工具来测试,下面的语句也可以用来测试磁盘随机读写IOPS:
 fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest

innodb_io_capacity越低,刷脏页的速度越慢,造成脏页的积累,导致频繁的阻塞,语句执行就会十分慢

  • 脏页比例,参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认值是 75%。InnoDB 会根据当前的脏页比例(假设为 M),算出一个范围在 0 到 100 之间的数字,这个计算用F(M)表示
    数据库相关基础_第55张图片
    脏页比例是通过 Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total 得到的,具体的命令参考下面的代码:
select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty';
select VARIABLE_VALUE into @b from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total';
select  @a/@b;
  • InnoDB 每次写入的日志都有一个序号,当前写入的序号跟 checkpoint 对应的序号之间的差值,我们假设为 N。InnoDB 会根据这个 N 算出一个范围在 0 到 100 之间的数字,这个计算公式可以记为 F2(N),N 越大,算出来的值越大
  • 刷脏页的速度
    根据上述运算得的 F1(M) 和 F2(N) 两个值,取其中较大的值记为 R,之后引擎就可以按照 innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度
    数据库相关基础_第56张图片

redo log的由来

最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力

Innodb log 所有参数 及 数量计算

bin log

  • bin log即为归档日志,Server 层的日志
  • bin log的作用
    1.保证主数据库和备份数据库之间的同步(主从数据库同步)、备份
    2.保证在较长一段时间内数据库丢失的数据能够找回并恢复,但是这个备份的时间越长,RTO(Recovery Time Objective,复原时间目标)越长
  • bin log 格式
    1.ROW格式:将每次执行的DML影响到的每一行记录都记录下来,对数据要求很精准时使用该种格式
    2.Statement格式:记录每次执行的DML,对数据要求不那么精准但是要求快速写、同步bin log时使用该种格式
    3.Mixed格式:MySQL根据情况自动选择ROW格式或Statement格式

MySQL 怎么知道 bin log 是完整的

  • 对于一个事务而言,不同格式的 bin log 有不同的验证方式
    1、statement 格式的 bin log,最后会有 COMMIT
    2、row 格式的 binlog,最后会有一个 XID event
    3、另外,在 MySQL 5.6.2 版本以后,还引入了 bin log checksum 参数,用来验证 binlog 内容的正确性
    对于 binlog 日志由于磁盘原因,可能会在日志中间出错的情况,MySQL 可以通过校验 checksum 的结果来发现,所以MySQL 还是有办法验证事务 binlog 的完整性的
    bin log的具体格式和影响

bin log和redo log的差别

1、bin log 是 MySQL 的 Server 层实现的,所有引擎都可以使用
2、redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;bin log 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”
3、redo log 是循环写的,空间固定会用完;bin log 是可以追加写入的,bin log 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志

binlog和redo log的工作流程

两阶段提交

即先有记录redo log的prepare阶段,然后才有记录完bin log后的commit阶段,核心是两个日志都记录以后再提交
数据库相关基础_第57张图片

两阶段提交的必要性

因为redo log 是InnoDB引擎特有日志,如果没有二阶段提交,引擎记录redo log日志和更新记录(事务提交)应该是几乎同时发生的
1、redo log & 事务提交 → 发生异常 → binlog
此时发生异常,根据bin log恢复数据,但是距离发生异常的最近一段时间进行更新操作无法恢复

2、binlog → 发生异常 → redo log & 事务提交
此时发生异常,根据根据binlog恢复数据,但是因为发生异常未能提交变更数据,但是恢复的数据库却是变更以后的

3、有了二阶段提交:

  • 当异常发生在redo log之前不影响数据库异常前后的数据异同
  • 当异常发生在redo log 和 bin log之间,因为redo log处于prepare阶段所以此时的更新操作没有实际执行,是无效的,不影响数据库异常前后的数据异同

4、异常发生在binlog之后
数据库相关基础_第58张图片

如何关联redo log 和 bin log

它们有一个共同的数据字段,叫 XID
崩溃恢复的时候,会按顺序扫描 redo log:
1、如果碰到既有 prepare、又有 commit 的 redo log,就直接提交
2、如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 bin log 找对应的事务,然后判断bin log是否完整,是则直接提交,否则回滚

只有bin log的情况

1、历史原因的话,那就是 InnoDB 并不是 MySQL 的原生存储引擎。MySQL 的原生引擎是 MyISAM,设计之初就有没有支持崩溃恢复。InnoDB 在作为 MySQL 的插件加入 MySQL 引擎家族之前,就已经是一个提供了崩溃恢复和事务支持的引擎了。InnoDB 接入了 MySQL 后,发现既然 binlog 没有崩溃恢复的能力,那就用 InnoDB 原有的 redo log 好了

2、功能上的原因,bin log 没有能力恢复“数据页”
数据库相关基础_第59张图片
如果在图中标的位置,也就是 binlog2 写完了,MySQL 发生了 crash,重启后,引擎内部事务 2 会回滚,然后应用 binlog2 可以补回来;但是对于事务 1 来说,系统已经认为提交完成了,不会再应用一次 binlog1,对于事务1的内容对应的数据页可能还在内存中并未刷脏页,此时随着crash遗失了

只有redo log的情况

1、redo log 是循环写,写到末尾是要回到开头继续写的。这样历史日志没法保留,redo log 也就起不到归档的作用

2、MySQL 系统依赖于 bin log。bin log 作为 MySQL 一开始就有的功能,被用在了很多地方。其中,MySQL 系统高可用的基础,就是 bin log 复制。还有很多公司有异构系统(比如一些数据分析系统),这些系统就靠消费 MySQL 的 bin log 来更新自己的数据。关掉 bin log 的话,这些下游系统就没法输入了

general_log

general_log参考general_log的设置、作用

你可能感兴趣的:(MySQL,数据库)