MYSQL一站式通关

前置

不用业务字段来定主键

基础

查询

any 任意的一个 可以认为是最大的或者最小的 有一个满足条件就行

all 表示所有

别名可以用""引起来 可以里面用空格

distinct 去重

`` 着重字段 用在关键字上(即你的名字和关键子重复)

/也可以用div

least 最小的

greatest 最大的

_表示一个不确定的字符

转义字符\

默认asc 升序

降序desc

列的别名只能在order by中使用

union 会执行去重

union all不会去重

等值连接 非等值连接

或者其他逻辑

自连接 非自连接

自己内部表的查询条件

内连接 外连接

内连接:inner join 所有的 就是外连接

join .. on..

left join on 左外连接

相关子查询:内查询用了外查询的数据(一行数据)

函数

avg

count

sum

基本数据类型:char vachar Text DECIMAL 精度和标度

如果超出了精度 整数和小数分开存储

推荐采用统一的书写规范:

数据库名、表名、表别名、字段名、字段别名等都小写

SQL 关键字、函数名、绑定变量等都大写

sql执行顺序

表之间的关系

  1. 一对一
  2. 一对多
  3. 多对多:必须创建中间表

SQL语句分类

  1. DDL 定义数据库
  2. DML 操作数据库
  3. DQL 查询数据库
  4. DCL 创建用户 控制访问权限

sql语法

create table 表名(字段1 类型 【comment '注释'】)

数据类型

UNSIGEN 表示无符号 (这个好像没怎么用)

mysql java 备注
tinyint Boolean/int 可以用来存储布尔 0/1
varchar String
CHAR String
INTEGER Long
TINYINT Integer
FLOAT Float
DOUBLE Double
DECIMAL BigDecimal
DATE java.sql.Date

精度DECIMAL:

DECIMAL(P,D);

  • P是表示有效数字数的精度。 P范围为1〜65。
  • D是表示小数点后的位数。 D的范围是0~30。MySQL要求D小于或等于(<=)P。

注意:在java8以后 尽量使用**LocalDate等系列api**


范式

第一范式

每个字段具有原子性,即字段不能被拆成两个字段(注:主观认为的)

反范式化

范式可以减少冗余,但也增加了查询的成本。

可以在性能的要求下进行反范式化

要求

  1. 冗余字段不需要经常修改
  2. 查询的时候 字段不可或缺

事物

事物分类

扁平事务

扁平事务虽然简单,但在实际生产环境中使用最为频繁,故每个数据库系统都实现了对扁平事务的支持\ 扁平事务 的 主要限制 是 不能提交 或者 回滚事务的某一部分,或 分几个步骤提交

带有保存点的扁平事务

带有保存点的扁平事务 能回滚到 任意正确的保存点

链事务

链事务中的回滚 仅限于 当前事务 ,即只能恢复到 最近一个的保存点

嵌套事务

分布式事务

事物的特征

  • A 原子性 (即一个事务里的操作,要么全部执行成功,要么全部失败
    • undo log保证(回滚日志
  • C 一致性 (即事务前后的操作整体数据是一致的
    • 这个是目的
  • I 隔离性 (即事务与事务的操作是互不影响的
    • 事物的读写靠MySQL的锁机制来保证隔离,事务间山东写操作靠MVCC机制(快照读,当前读)来保证隔离性
  • D 持久性 (即事务一旦完成,就会被持久化到磁盘中永久保存,不会丢失
    • redolog将数据保存在磁盘中

事务中的一些问题

更新丢失

两个事务在某一时刻对同一数据进行读取后,先后进行修改,导致第一次操作的数据丢失

AB操作同一数据,A进行修改,B再次更改时失败进行回滚

脏读

一个事务在执行过程中读取到别的事务还没提交的数据

不可重复读

数据被修改

**幻读 **

多了一条数据

隔离级别

分布式事物:

分布式事务的解决方案


扁平事务

扁平事务虽然简单,但在实际生产环境中使用最为频繁,故每个数据库系统都实现了对扁平事务的支持\ 扁平事务 的 主要限制 是 不能提交 或者 回滚事务的某一部分,或 分几个步骤提交

带有保存点的扁平事务

带有保存点的扁平事务 能回滚到 任意正确的保存点

链事务

链事务中的回滚 仅限于 当前事务 ,即只能恢复到 最近一个的保存点

嵌套事务

分布式事务

MVCC

可重复读重点在于update和delete,幻读在于insert

各种锁

  • 按照锁的使用方式,MySQL锁可以分成共享锁、排它锁两种;
  • 共享群,fot share,in share mode
  • 排他锁 for update
  • 根据加锁的范围,MySQL锁大致可以分成全局锁、表级锁和行锁三类;
  • 从思想层面上看,MySQL锁可以分为悲观锁、乐观锁两种;

悲观锁:正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据

在mysql中的实现,关闭自动提交的属性,并且使用select * for update

如果有索引,默认是行锁,不然就是表锁

间隙锁:锁住的是一个区间,而不仅仅是这个区间的每一条数据

举例来说,假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101,下面的SQL:

sql 复制代码
SELECT * FROM emp WHERE empid > 100 FOR UPDATE

当我们用条件检索数据,并请求共享或排他锁时,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的"间隙"加锁。

这个时候如果你插入empid等于102的数据的,如果那边事物还没有提交,那你就会处于等待状态,无法插入数据。

1、临键锁(Next-Key Locks)

Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁

也可以理解为一种特殊的间隙锁 。通过临建锁 可以解决幻读的问题。 每个数据行上的非唯一索引列 上都会存在一把临键锁 ,当某个事务持有该数据行的临键锁 时,会锁住一段左开右闭区间 的数据。需要强调的一点是,InnoDB 中行级锁 是基于索引实现的,临键锁 只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

假设有如下表:

InnoDBRR隔离级别:id主键, age 普通索引

3.jpg

该表中 age 列潜在的临键锁有:\ (-∞, 10],\ (10, 24],\ (24, 32],\ (32, 45],\ (45, +∞],\ 在事务 A 中执行如下命令:

sql 复制代码
-- 根据非唯一索引列 UPDATE 某条记录 
UPDATE table SET name = Vladimir WHERE age = 24; 
-- 或根据非唯一索引列 锁住某条记录 
SELECT * FROM table WHERE age = 24 FOR UPDATE;

不管执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:

sql 复制代码
INSERT INTO table VALUES(100, 26, 'tianqi');

很明显,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (24, 32] 这个区间内的临键锁。

总结

  • InnoDB 中的行锁 的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁。
  • 记录锁 存在于包括主键索引 在内的唯一索引中,锁定单条索引记录。
  • 间隙锁 存在于非唯一索引 中,锁定开区间 范围内的一段间隔,它是基于临键锁实现的。
  • 临键锁 存在于非唯一索引 中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁 ,锁定一段左开右闭的索引区间。

实现原理

隐藏字段+undolog版本链+ReadView

在innodb中,会在每行的数据后额外添加两个隐藏的值来实现mvcc,这两个值,一个记录这行数据何时被创建,另外一个记录这行数据何时过期。在实际操作中,存储的不是时间,而是事物的版本号,每开一个事物,事物的版本号就会递增。

可重复读:

  • select时 读取创建版本号<=当前事物版本号,删除版本号为空或者>当前事物版本号
  • insert时 保存当前事物版本号为行的创建版本号
  • delete时 保存当前事物版本号为行的删除版本号
  • update 插入一条新纪录,保存当前事物版本号为行的创建版本号,同时保存当前事物版本号到原来删除的行

索引

  • 聚簇索引 指索引的键值的逻辑顺序与表中相应行的物理顺序一致,即每张表只能有一个聚簇索引,也就是我们常说的主键索引
  • 非聚簇索引的逻辑顺序则与数据行的物理顺序不一致。

索引

B+树

特点:

1)B+ 树本质是一棵查找树,自然支持范围查询和排序。

2)在符合某些条件(聚簇索引、覆盖索引等)时候可以只通过索引完成查询,不需要回表。

3)查询效率比较稳定,因为每次查询都是从根节点到叶子节点,且为树的高度。

一页16kb

每个节点(页)包含以下元素:

  1. 键(keys):按照升序排序的键值
  2. 指针
    1. 内部指针:每个键跟随一个指向子节点的指针
    2. 叶子节点:每个键后跟随数据记录的地址(或直接存储数据),且叶子节点之间通过指针形成链表
  3. 槽(slots):每个键和指针对 称之为一个槽,按顺序存储

索引是一种数据结构(排序+查找)

什么时候建立索引

  • 作为经常查询,联表的字段
  • 经常排序,分组,联合操作的字段
    • order by 、group by、Distinct、union
    • 不然可能就会有filesort问题
  • 具有唯一性约束的字段
  • order group的
  • 字符串取前n个字符进行创建索引

索引下推,索引覆盖,最左匹配

覆盖索引:要查的字段都在索引中

索引下推:在一个b+树中 把能筛选的条件进行筛选

最左匹配:顾名思义,最左优先,以最左边为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。

索引失效

不是说一定失效 而是要优化器考虑成本。如果回表的成本大于索引,那就可以使用索引

隐式转换

范围条件右边的失效

!= 索引失效

order by不limt 索引失效

1.1 为什么会有碎片?

  1. MySQL 中 insert 与 update 都可能导致页分裂,这样就存在碎片。

对于大量的UPDATE,也会产生文件碎片化 , Innodb的最小物理存储分配单位是页(page),而UPDATE也可能导致页分裂(page split),频繁的页分裂,页会变得稀疏,并且被不规则的填充,所以最终数据会有碎片。

  1. delete 语句实际上只是给数据打个标记,并且记录到一个链表中,这样就形成了留白空间。

在InnoDB中,删除一些行,这些行只是被标记为"已删除",而不是真的从索引中物理删除了,因而空间也没有真的被释放回收。InnoDB的Purge线程会异步的来清理这些没用的索引键和行。

  1. 当执行插入操作时,MySQL会尝试使用空白空间,但如果某个空白空间一直没有被大小合适的数据占用,仍然无法将其彻底占用,就形成了碎片;
  2. 总结:
    1. 表的增删改操作,可能会造成数据空洞的,当对表进行大量的增删改操作后,数据空洞存在的可能性比较大。

日志

持久性是通过 redo log (重做日志)来保证的;

原子性是通过 undo log(回滚日志) 来保证的;

隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;

一致性则是通过持久性+原子性+隔离性来保证;

  • undolog(回滚日志) 保证原子性,主要应用于事务回滚和MVCC
  • redolog (重做日志) 保证持久性,用于掉电恢复
  • binlog (归档日志) 数据备份和主从复制

undo log

在事务没提交之前,mysql会记录更新前的数据在undolog中

格式

  • trx_id 事务
  • roll_pointer 版本链

redolog

更新数据的时候,首先更新内存,然后将本次对这个页的修改以redolog的形式记录下来。

后续,innodb引擎会在合适的时候,由后台线程将缓存在buffer pool的脏页刷新到磁盘里,这就是WAL技术(Write-Ahead Logging)。指的就是mysql的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时候再写进磁盘上

:::info redo log 是物理日志,记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。

在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。

当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。

:::

什么时候进行刷盘

实际上, 执行一个事务的过程中,产生的 redo log 也不是直接写入磁盘的,因为这样会产生大量的 I/O 操作,而且磁盘的运行速度远慢于内存。

所以,redo log 也有自己的缓存------ redo log buffer,每当产生一条 redo log 时,会先写入到 redo log buffer。

有三种策略:

  1. 每次都不刷盘,将数据留在缓存中
  2. 每次提交事务都刷盘
  3. 写进操作系统的缓存中(page cache)

因为会有innodb的后台线程,一秒钟刷盘一次。

3比1安全一点,因为是操作系统挂了才会丢失数据。

binlog

前面介绍的 undo log 和 redo log 这两个日志都是 Innodb 存储引擎生成的。

MySQL 在完成一条更新操作后,Server层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件。

此处为语雀卡片,点击链接查看

什么时候刷盘:

在事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 文件中,并清空 binlog cache。

write表示将文件写进操作系统缓存

fsync表示真正的进行磁盘io来刷盘

参数sync_binlog

  • 0: 表示每次提交事务都write,不fsync,让操作系统来进行持久化到磁盘
  • 1: 每从提交事务都刷盘
  • N: 表示每次都write ,但是N个事务后才刷盘

SQL优化

整理流程

  1. 在运维平台或者别的工具上看sql是否执行慢
  2. 如果慢,在where ,or,排序等字段上加索引
  3. 如果已经加上索引,但是没生效,就要进行排查
  4. explain type字段 如果是all,没生效
  5. 小表驱动大表

type:

system 系统表

const 唯一索引(主键

eq_ref 连表 等值查询

ref 连表查询 普通非唯一索引

range 索引 范围

index 扫描全索引


架构

分为

  1. 连接层
  2. 服务层
  3. 引擎层

查询过程

存储引擎

MyISAM

InnoDB

  • 支持外键
  • 支持事务
  • 行级锁
  • 缓存 索引和数据一起缓存

高并发 事务 更大资源


底层

缓冲池:

Buffer Pool 一次只能允许一个线程来操作,一次只有一个线程来执行这一系列的操作,因为MySQL 为了保证数据的一致性,操作的时候必须缓存池加锁,一次只能有一个线程获取到锁

池里面有两个双向链表

  • free链表,记录空闲的内存
  • flush链表,脏数据(指没同步到磁盘中的) 定期同步

内部还是用了LRU缓存

冷热数据-LRU

redolog

当进行增删改操作时,MySQL 会在更新 Buffer Pool 中的缓存页数据时,会记录一条对应操作的 redo log 日志,这样如果出现 MySQL 宕机或者断电时,如果有缓存页的数据还没来得及刷入磁盘,那么当 MySQL 重新启动时,可以根据 redo log 日志文件,进行数据重做,将数据恢复到宕机或者断电前的状态,保证了更新的数据不丢失,因此 redo log 又叫做重做日志。它的本质是保证事务提交后,更新的数据不丢失。

redo log buffer

当一条 SQL 更新完 Buffer Pool 中的缓存页后,就会记录一条 redo log 日志,前面提到了 redo log 日志是存储在磁盘上的,那么此时是不是立马就将 redo log 日志写入磁盘呢?显然不是的,而是先写入一个叫做 redo log buffer 的缓存中,redo log buffer 是一块不同于 buffer pool 的内存缓存区,在 MySQL 启动的时候,向内存中申请的一块内存区域,它是 redo log 日志缓冲区,默认大小是 16MB,由参数 innodb_log_buffer_size 控制(前面的截图中可以看到)。

redo log buffer 内部又可以划分为许多 redo log block,每个 redo log block 大小为 512 字节。我们写入的 redo log 日志,最终实际上是先写入在 redo log buffer 的 redo log block 中,然后在某一个合适的时间点,将这条 redo log 所在的 redo log block 刷入到磁盘中。

这个合适的时间点究竟是什么时候呢?

  1. MySQL 正常关闭的时候;
  2. MySQL 的后台线程每隔一段时间定时的讲 redo log buffer 刷入到磁盘,默认是每隔 1s 刷一次;
  3. 当 redo log buffer 中的日志写入量超过 redo log buffer 内存的一半时,即超过 8MB 时,会触发 redo log buffer 的刷盘;
  4. 当事务提交时,根据配置的参数 innodb_flush_log_at_trx_commit 来决定是否刷盘。如果 innodb_flush_log_at_trx_commit 参数配置为 0,表示事务提交时,不进行 redo log buffer 的刷盘操作;如果配置为 1,表示事务提交时,会将此时事务所对应的 redo log 所在的 redo log block 从内存写入到磁盘,同时调用 fysnc,确保数据落入到磁盘;如果配置为 2,表示只是将日志写入到操作系统的缓存,而不进行 fysnc 操作。(进程在向磁盘写入数据时,是先将数据写入到操作系统的缓存中:os cache,再调用 fsync 方法,才会将数据从 os cache 中刷新到磁盘上)
如何保证数据不丢失

前面介绍了 redo log 相关的基础知识,下面来看下 MySQL 究竟是如何来保证数据不丢失的。

  1. MySQL Server 层的执行器调用 InnoDB 存储引擎的数据更新接口;
  2. 存储引擎更新 Buffer Pool 中的缓存页,
  3. 同时存储引擎记录一条 redo log 到 redo log buffer 中,并将该条 redo log 的状态标记为 prepare 状态;
  4. 接着存储引擎告诉执行器,可以提交事务了。执行器接到通知后,会写 binlog 日志,然后提交事务;
  5. 存储引擎接到提交事务的通知后,将 redo log 的日志状态标记为 commit 状态;
  6. 接着根据 innodb_flush_log_at_commit 参数的配置,决定是否将 redo log buffer 中的日志刷入到磁盘。

将 redo log 日志标记为 prepare 状态和 commit 状态,这种做法称之为两阶段事务提交,它能保证事务在提交后,数据不丢失。为什么呢?redo log 在进行数据重做时,只有读到了 commit 标识,才会认为这条 redo log 日志是完整的,才会进行数据重做,否则会认为这个 redo log 日志不完整,不会进行数据重做。

例如,如果在 redo log 处于 prepare 状态后,buffer pool 中的缓存页(脏页)也还没来得及刷入到磁盘,写完 biglog 后就出现了宕机或者断电,此时提交的事务是失败的,那么在 MySQL 重启后,进行数据重做时,在 redo log 日志中由于该事务的 redo log 日志没有 commit 标识,那么就不会进行数据重做,磁盘上数据还是原来的数据,也就是事务没有提交,这符合我们的逻辑。

实际上要严格保证数据不丢失,必须得保证 innodb_flush_log_at_trx_commit 配置为 1。

如果配置成 0,则 redo log 即使标记为 commit 状态了,由于此时 redo log 处于 redo log buffer 中,如果断电,redo log buffer 内存中的数据会丢失,此时如果恰好 buffer pool 中的脏页也还没有刷新到磁盘,而 redo log 也丢失了,所以在 MySQL 重启后,由于丢失了一条 redo log,因此就会丢失一条 redo log 对应的重做日志,这样断电前提交的那一次事务的数据也就丢失了。

如果配置成 2,则事务提交时,会将 redo log buffer(实际上是此次事务所对应的那条 redo log 所在的 redo log block )写入磁盘,但是操作系统通常都会存在 os cache,所以这时候的写只是将数据写入到了 os cache,如果机器断电,数据依然会丢失。

而如果配置成 1,则表示事务提交时,就将对应的 redo log block 写入到磁盘,同时调用 fsync,fsync 会将数据强制从 os cache 中刷入到磁盘中,因此数据不会丢失。

从效率上来说,0 的效率最高,因为不涉及到磁盘 IO,但是会丢失数据;而 1 的效率最低,但是最安全,不会丢失数据。2 的效率居中,会丢失数据。在实际的生产环境中,通常要求是的是"双 1 配置",即将 innodb_flush_log_at_trx_commit 设置为 1,另外一个 1 指的是写 binlog 时,将 sync_binlog 设置为 1,这样 binlog 的数据就不会丢失(后面的文章中会分析 binlog 相关的内容)。


集群

主从复制

待整理

InnoDB锁

InnoDB实现了以下两种类型的行锁

  • 共享锁(S锁、行锁):多个事务对同一数据行可以共享一把锁,只能读不能修改
  • 排它锁(X锁、行锁):一个事务获取一个数据行的排它锁,那么其他事务将不能再获取该行的锁(共享锁、排它锁), 允许获取排他锁的事务更新数据

对于UPDATE,DELETE,INSERT操作, InnoDB会自动给涉及及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,

而且因为InnoDB引擎允许行锁和表锁共存,实现多粒度锁机制,使用意向锁实现表锁机制,

  • 意向共享锁(IS锁、表锁):当事务准备给数据行 加共享锁时,会先给 加上一个意向共享锁。意向共享锁之间是兼容的
  • 意向排它锁(IX锁、表锁):当事务准备给数据行加排它锁时,会先给表加上一个意向排它锁。意向排它锁之间是兼容的

意向锁(IS、IX)是InnoDB数据操作之前自动加的,不需要用户干预。它的意义在于:当事务想去进行锁表时,可以先判断意向锁是否存在,存在时则可快速返回该表不能启用表锁,否则就需要等待,


不分类

8.0新特性:

markdown 复制代码
1. 降序索引

在两个字段进行索引时 可指定两个字段 使用降序还是升序(默认升序====两个都可以指定)

markdown 复制代码
2. 隐藏索引
相关推荐
追逐时光者30 分钟前
6种流行的 API 架构风格,你知道几种?
后端
小麦果汁吨吨吨1 小时前
Flask快速入门
后端·python·flask
kinlon.liu1 小时前
SpringBoot整合Redis限流
spring boot·redis·后端
小p3 小时前
迈向全栈:服务器上的软件安装
前端·后端
Bohemian3 小时前
浅谈Golang逃逸分析
后端·面试·go
用户1529436849593 小时前
谷歌云代理商:如何配置谷歌云服务器的端口转发?
后端
努力的搬砖人.3 小时前
Spring Boot集成MinIO的详细步骤
java·spring boot·后端
货拉拉技术4 小时前
订单哨兵OrderSentinel平台介绍
javascript·后端·程序员
精神内耗中的钙奶饼干4 小时前
Springboot整合kafka记录
后端·kafka
JavaGuide4 小时前
IntelliJ IDEA 2025.1 发布!Java 24 支持、AI 重大更新!!
后端·intellij idea