Home MySQL知识点汇总
Post
Cancel

MySQL知识点汇总

MySQL select语句执行

  • prepare 阶段,检查查询语句中的表活字段是否存在,将 * 拓展为表上的所有列。
  • optimize 阶段,优化器决定使用哪个索引。
  • execute 阶段,执行器,索引下推。

表空间文件结构

表空间 -> 段 -> 区 -> 页 -> 行

页:InnoDB的数据按为单位读写,默认每个页大小为16KB,意味着一次最少从磁盘读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。表中的记录存储在数据页中。B+树的每个节点都是一个数据页。

区:数据量大的时候,索引分配空间则是按为单位分配,每个区的大小为1MB,对于16KB的页来说,连续64个页会被划分到一个区,这样,B+树中节点所构成的链表中相邻页的物理位置也相邻,便能顺序IO。

段:索引段、数据段、回滚段。

  • 索引段:存放B+树非叶子结点的区的集合
  • 数据段:存放B+树叶子结点的区的集合
  • 回滚段:存放的是回滚数据的区的集合,MVCC利用回滚段实现了多版本查询数据。

行格式

  • Redundant
  • Compact,一条完整的记录分为“记录的额外信息”和“记录的真实数据”,额外信息中又分为变长字段长度列表、NULL值列表和记录头数据。变长字段长度列表和NULL值列表都是倒序保存,并且不是必须的,只要没有变长字段或NULL值字段即可。8个字段值可以为NULL,那么NULL值列表空间为1字节,9字段则2字节。
  • Dynamic
  • Compressed

Varchar(n) n最大取多少?

MySQL 规定除了 TEXT、BLOBs 这种大对象类型之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过 65535 个字节。注意,是一行数据的最大字节数 65535,其实是包含「变长字段长度列表」和 「NULL 值列表」所占用的字节数的。

行格式溢出后,数据存放到溢出页

B树和B+树

  • B树(多路的平衡搜索树),节点可以存储多个数据,并且每个节点存放索引和数据,一个节点的关键字等于该节点子节点个数减一。查询的最好情况是O(1),一般高度为3。
  • B+树,非叶子节点关键字个数等于子节点个数
    1. 叶子节点保存数据(数据页),非叶子节点保存索引,查询固定为O(logn),并且由于这个特点更适合外部存储。
    2. 叶子节点有指向下一个叶子节点的指针,双向链表,可顺序访问,因此区间访问的性能较好。
    3. 存在重复元素,因为非叶子结点不保存数据。

与红黑树的比较,B+树的优势:

  1. 更少的查询次数,因为B+树的树高一般小
  2. 利用计算机的预读特性,因为B+树具有类似链表的特点,因此相邻的节点也能被预先载入

为什么不使用跳表:MySQL 一次数据页加载都需要一次磁盘IO。并且磁盘IO的次数和树高有关系,又因为跳表的高度一般比B+树高,所以查询速度会大大降低,因此不适合。

为什么 Redis 使用了跳表:因为 Redis 不存在磁盘IO。重点在于磁盘IO次数。

MySQL索引

给表添加索引时,是会对表加锁,因此在生产环境中不能直接添加索引。

数据结构维度:

  1. B+树索引,适合范围查询,复杂度为O(logn)。
  2. 哈希索引,能以O(1)时间进行查找,但失去有序性,无法用于排序和分组,只支持精确查找(等值查找)。
  3. 全文索引,使用倒排索引实现,记录关键词到所在文档的映射,一般在text, varchar上创建。
  4. R-Trees索引,MyISAM支持的索引类型,和空间地理数据有关。

物理存储维度(InnoDB):

  1. 聚簇索引,叶节点存放一整行记录。一个表只能拥有一个聚簇索引。
  2. 非聚簇索引(二级索引),叶节点只存放主键信息,所以一般需要回表查询。

逻辑维度

  1. 主键索引,不允许空值
  2. 普通索引,没任何限制
  3. 联合索引,多个字段创建的索引,使用时遵循左前缀原则。
  4. 唯一索引,索引列中的值必须是唯一的,但可以为空值。
  5. 空间索引

索引失效?主要看是否回表或者是查询的数据量过大

  1. 不满足最左匹配(但也需要看是否是覆盖索引,例如对于联合索引(col1,col2,col3),查询语句SELECT col1,col2,col3 FROM test WHERE col2=2;也能够触发索引,并且索引截断的情况可以会通过索引下推解决)
  2. 尽可能明确查询列,而不是select *,即使不满足最左匹配(可以用于性能优化)
  3. 索引列参与运算
  4. 索引列使用函数
  5. 错误的like使用(当模糊匹配的占位符位于条件的首部,并且要看数据库中的字段)
  6. 类型隐式转换(参数类型和字段类型不匹配,例如 where no = 1,但no实际上是varchar类型的)
  7. 错误的or使用(切记两个条件都要添加索引,否则会导致索引失效,or两边同时使用 < 和 >,也会失效)
  8. 两列做比较
  9. 错误的 <> 和 != 使用(查询结果集占比较大时索引会失效)
  10. is not null(is null 走索引)
  11. not in(条件列是主键时走索引)
  12. not exists
  13. orderby(部分会失效)
  14. 范围过大的 between and

索引跳跃扫描

最左缀原则可以通过跳跃扫描的方式打破,当第一列索引的唯一值较少时,即使where条件没有第一列索引,查询的时候也可以用到联合索引。

覆盖索引

指查询列被所建的索引覆盖,覆盖索引是 select 的数据列只需要从索引中就能取到,不必回表。

哪些情况下不适合建索引

  • 数据量少
  • 更新频繁
  • 区分度低
  • where, groupby, orderby 后没有使用到的字段
  • 联合索引中的索引

索引下推

指将部分上层(服务层)负责index filter的事情,交给了下层(引擎层)去处理。它能减少二级索引再查询时的回表查询次数,提高查询效率。只适用于二级索引。

EXPLAIN 命令的extra一栏中有Using index condition,表明使用了索引下推

举例:select * from table1 where b like '3%' and c = 3

5.6 之前:

  • 先通过 联合索引 查询到 开头为 3 的数据 然后拿到主键
  • 然后通过主键去主键索引里面去回表查询 二级索引里面查询出来几个 3 开头的就回表几次

5.6 之后:

  • 先通过 二级索引 查询到开头为 3 的数据 然后 再找到 c = 3 的数据进行过滤 之后拿到主键
  • 通过主键进行回表查询,减少了回表次数

建索引的原则

  1. 选择唯一性索引
  2. 为经常需要orderby,groupby操作的字段建立索引
  3. 经常作为查询条件的字段建立索引
  4. 限制索引数量
  5. 尽量使用数据量少的索引
  6. 尽量使用最左前缀匹配原则
  7. 使用区分度高的列作为索引
  8. 扩展索引而不是新建索引

事务的隔离

MVCC: Read view + undo log + 2个隐藏字段(版本链),为了解决读写冲突

  • Read View 就是一个快照,保存某个时刻的数据信息。其中也有四个字段,分别是creater_trx_id, m_ids, min_trx_id, max_trx_id。
  • undo log是存放更新前的数据(快照),保存了历史快照
  • 隐藏字段:row_trx_id 和 roll_pointer,前者表示更新行数据的事务id,后者是只想改行数据上一个版本的undo log的回滚指针

MVCC(Multiversion Concurrency Control)的两种读:

  1. 快照读,不加锁,在事务中第一次SELECT语句执行时生成,只有在本事务中对数据进行更改才会更新快照,通过MVCC的方式解决幻读。实现的方式是启动事务后,在执行第一个查询语句后,会创建一个 Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的
  2. 当前读,加间隙锁(行锁)解决幻读,(FOR UPDATE)
    • 查询条件不走索引,退化成表锁
    • 查询条件走普通索引,锁住查询条件附近的间隙,等于间隙锁
    • 查询条件走唯一索引,只锁一条数据,等价于记录锁

三种问题:

  1. 脏读,指读取未提交数据
  2. 不可重复读,指在同一事务内读到的数据是不一致的
  3. 幻读,指事务在查询的过程中,有另外一个事务对范围内新增了记录,导致范围查询的结果条数不一致的现象。

SQL的四种隔离级别:

  1. 读未提交,可能出现脏读
  2. 读已提交,可能出现不可重复读
  3. 可重复读,可能出现幻读,但使用MVCC + 间隙锁能解决大部分幻读现象
  4. 串行化,对记录加上读写锁

MVCC + 间隙锁在RR的隔离级别下能解决大部分幻读情况

  • MVCC 快照读的情况下,多次查询获取的是同一个快照数据。
  • 间隙锁,其他事务的插入操作会被阻塞。
  • 解决不了的幻读情况:(MVCC只对于插入数据后的当前读,更新,删除等操作(不使用快照读),仍然存在幻读的问题)
    1. 事务A快照读,事务B新增数据并提交,接着事务A修改事务B新增的数据并快照读,此时发生幻读。
    2. 事务A快照读,事务B新增数据并提交,接着事务A当前读,此时发生幻读。

若要尽可能避免幻读现象的发生,尽量在开启时候后马上执行当前读。

WAL 机制

WAL(Write-ahead logging)指 MySQL 在执行写操作时先记录在日志中,后更新到磁盘中。

WAL 核心:将随机写(磁盘的写操作是随机IO,耗性能)转变成顺序写和组提交机制,降低客户端延迟,提高吞吐量。

三大日志binlog, redolog, undolog:

  1. Buffer Pool是MySQL进程管理的一块内存空间,有减少磁盘IO次数的作用。
  2. redo log重做日志是InnoDB存储引擎的一种物理格式的日志,用来实现事务持久性,主要有两部分文件组成,再内存中的重做日志缓冲(redo log buffer)以及磁盘中的重做日志文件(redo log),(循环写,数据会被覆盖)。使用场景:崩溃恢复,在发生故障的时间点,尚有脏页未写入磁盘,在重启 mysql 服务的时候,根据 redo log 进行重做,从而达到事务的持久性这一特性。
  3. undo log回滚日志是InnoDB存储引擎的一种逻辑格式的日志,记录的是数据的逻辑变化,保证的是数据库的原子性,比如一条insert语句对应的是一条delete的undo log,在发生事务错误时,就能回滚到事务之前的数据状态;MVCC,事务未提交前,undo log 保存了未提交的版本数据,作为旧版本的快照数据,类似于做备份。
  4. binlog是MySQL Server层的一种逻辑格式的日志,用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。使用场景:主从复制 :在 Master 端开启 binlog ,然后将 binlog 发送到各个 Slave 端, Slave 端重放 binlog 从而达到主从数据一致;数据恢复,通过使用 mysqlbinlog 工具再结合 binlog 文件,可以将数据恢复到过去的某一时刻。

两阶段提交:

  • prepare阶段:先将数据修改写入redo log,并将其标记为 prepare 状态,将相应的 sql 语句写入 binlog
  • commit阶段:将 redo log 标记为 commit 状态,然后根据 sync_binlog 参数的设置,决定是否将 binlog 刷盘。

如果发生崩溃,可以根据redo log恢复数据页的状态(让InnoDB存储引擎拥有了崩溃恢复能力),也可以根据 binlog 恢复sql语句的执行(保证了MySQL集群架构的数据一致性)。通过两阶段的流程可以保证任何时刻 redo log 和 binlog 在逻辑上一致。

为什么需要两阶段提交?

  • 以基本的事务为单位,redo log 在事务执行过程中可以不断写入,而 binlog 只有在提交事务时才写入,所以redo log 与 binlog 的写入时机不一样。为了解决两份日志之间的逻辑一致问题,将redo log的写入拆成了两个步骤 prepare 和 commit,即两阶段提交。如果只有 redo log 或者只有 binlog,那么事务就不需要两阶段提交。

执行顺序

  1. from
  2. on
  3. join
  4. where
  5. group by
  6. having
  7. select
  8. distinct
  9. order by
  10. limit

每一步执行时都会产生一个虚拟表,都会被用作下一个步骤的输入。

乐观锁、悲观锁

  • 乐观锁:不能解决脏读问题
  • 悲观锁:select … for update

全局锁、表级锁、行锁

  • 全局锁:对整个数据库实例加锁,处于只读状态,其命令是Flush tables with read lock(FTWRL),用于全库逻辑备份。
  • 表级锁:开销小,加锁快,不会出现死锁,发生锁冲突的概率最高,并发度最低。适合以查询为主。
    • 表锁,可以添加表级别的共享锁或排他锁,锁的粒度较粗。
    • 元数据锁(MDL),CRUD时,数据库自动给表加上MDL读锁;变更表结构时,自动加MDL写锁。事务提交后才会释放MDL锁。
    • 意向锁,在执行插入、更新、删除需要加独占锁之前,需要对表先加上意向独占锁,其目的是快速判断表里是否有记录被加锁。
    • AUTO-INC锁,自增锁,插入数据时获取该锁,插入语句完成后,释放锁。
  • 行锁:开销大,加锁慢,会出现死锁,发生锁冲突的概率最低,并发读也高。只有InnoDB引擎支持。行锁依赖于索引,如果加锁操作没有使用到索引,那么会退化成表锁。
    • 记录锁:锁定一条记录,存在 S 型记录锁和 X 型记录锁。加了 S 型记录锁后可以继续加 S 型记录锁,但不能加 X 型记录锁;加了 X 型记录锁后不能加 S 型或 X 型记录锁。存在于包含主键索引的唯一索引中。
    • 间隙锁:锁定一个范围,但不包含记录本身,只存在于可重复读隔离级别,为了解决可重复读隔离级别下幻读的现象。存在于非唯一索引中。插入意向锁是一种表明插入意向的间隙锁。
    • 临键锁:锁定一个范围,前开后闭,存在于非唯一索引中。

共享锁(读锁)、排他锁(写锁)

  • 共享锁:读锁,其他事务可以读,但不能写,select lock in share mode
  • 排他锁:写锁,其他事务不能读,也不能写,select for update

mysql 数据实时同步到Es

常见的数据同步方案主要有以下三种:

  1. 同步调用。在实现增删改的同时,通过调用ES所在服务提供的接口。
  2. 异步调用。增删改服务和搜索服务分别通过MQ进行发送和监听消息,有效降低业务的耦合度,但较为依赖MQ的性能;
  3. binlog监听。给MySQL开启binlog功能,搜索服务基于canal监听binlog变化,但开启binlog会增加数据库负担、同时实现复杂度较高。

但也有异步调用和binlog监听结合在一起的例子,用canal作slave节点。

分库/分表

分为 水平切分(又称为 Sharding)垂直切分

切分策略

  • 范围切分,例如每一千万条数据一张表
  • 哈希切分,对分表键进行哈希运算,主流方法
  • 映射表,将分表键和数据库表对映射关系记录在表上

分布式ID

建议使用雪花算法,其中序列号一直保持递增,不会因为时间戳的不同而归零,防止被猜测上下文ID。

This post is licensed under CC BY 4.0 by the author.

Basics of Java

线段树(Java实现)