mySQL技术的方方面面,不管是应用还是面试,看这一文就够了

10255.jpg

mysql

1 基本知识

  • 支持分区
  • 默认innodb引擎

1-1 常用引擎

  • myisam
    my-z[ei]m
  • innodb
    in-no-db

2 数据类型

  • 整数,tiny int 1位 127/255,small int 2位,medium int 3位,int 4位,big int 8位

2-1 定点数

  • decimal(m,d) m 1~65,d 0~30 默认0
  • numeric(m,d)

2-2 浮点数

  • m 0~255,d 0~30
  • double(m,d) 只保证16位有效数字,超出=decimal
  • float(m,d) =double real_as_float
  • real(m,d) =double

2-3 二进制

  • bit(m) m 1~64
  • binary
  • varbinary
  • blob

2-4 字符串

  • char(m) m 0~255 固定长度,不够右边空格填充,取出时截掉
  • varchar(m) m 0~65535 ,与字符集有关系
  • text

2-5 enum

  • 单选字符串数据类型
  • 最多 65535

2-6 set

  • 多选字符串数据类型
  • 最多64个选项

2-7 时间日期类型

  • date,time,datatime,timestamp

datatime

  • 范围 全范围
  • 8个字节存储
  • 与时区无关

timestamp

  • 范围 1970 ~ 2037
  • 4位存储
  • 与时区有关,不同时区数据不一样

毫秒,微秒问题

  • mysql 没有毫秒,微秒数据,需要程序自行处理
  • mysql5.6.4 后支持,datetime(3/6)或者timestamp(3/6)

3 约束

3-1 主键 ,联合主键

  • 会生成唯一索引
  • 加no null 约束
  • 产生聚集索引

3-2 外键

  • 会生成索引

3-3 约束

唯一约束

  • 会生成唯一索引
  • 可以为空
  • 产生非聚集索引
  • 区别:目的不同,唯一约束约束数据,唯一索引优化查询
  • no null
  • 高可用方式
  • sql优化

4 索引

4-1 底层结构

背景知识

  • 系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一磁盘块中的数据会被一次性读取出来,而不是按需读取。
  • innodb 存储引擎使用页作为数据读取单位,页是其磁盘管理的最小单位,默认 page 大小是 16k。
  • 系统的一个磁盘块的存储空间往往没有这么大,因此 innodb 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16kb。

btree

  • 如比查找29关键字

模拟查找关键字 29 的过程:

  • 1.根据根节点找到磁盘块 1,读入内存。【磁盘 i/o 操作第 1 次】
  • 2.比较关键字 29 在区间(17,35),找到磁盘块 1 的指针 p2。
  • 3.根据 p2 指针找到磁盘块 3,读入内存。【磁盘 i/o 操作第 2 次】
  • 4.比较关键字 29 在区间(26,30),找到磁盘块 3 的指针 p2。
  • 5.根据 p2 指针找到磁盘块 8,读入内存。【磁盘 i/o 操作第 3 次】
  • 6.在磁盘块 8 中的关键字列表中找到关键字 29。

特性

  • 1、关键字集合分布在整颗树中;
  • 2、任何一个关键字出现且只出现在一个节点中;
  • 3、每个节点存储data和key;
  • 4、搜索有可能在非叶子节点结束;
  • 5、一个节点中的key从左到右非递减排列;
  • 6、所有叶节点具有相同的深度,等于树高h。
  • 每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。

b+tree(innodb举例)

设计初衷

  • innodb 存储引擎在设计时是将根节点常驻内存的,力求达到树的深度不超过 3,也就是说 i/o 不需要超过 3 次。
  • 分析b-tree的查找过程,发现需要3次磁盘 i/o 操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。
  • 在 b-tree 中,每个节点中有 key,也有 data,而每一个页的存储空间是有限的,如果 data 数据较大时将会导致每个节点(即一个页)能存储的 key 的数量很小,会导致 b-tree 的深度较大,增大查询时的磁盘 i/o 次数,进而影响查询效率。。

数据结构图

  • b+tree的检索过程

在b-tree的基础上有两点变化:

  • 数据是存在叶子节点中的;
  • 数据节点之间是有指针指向的。

page结构

page是整个innodb存储的最基本构件,也是innodb磁盘管理的最小单位,与数据库相关的所有内容都存储在这种page结构里。

page常见页类型
  • 数据页(b-tree node)
  • undo页(undo log page)
  • 系统页(system page)
  • 事务数据页(transaction system page)等。
  • 单个page的大小是16k(编译宏univ_page_size控制),每个page使用一个32位的int值来唯一标识,这也正好对应innodb最大64tb的存储容量(16kib * 2^32 = 64tib)。

page的基本结构

  • page的头部保存了两个指针,分别指向前一个page和后一个page,page链接起来就是一个双向链表的结构。
主体内容

page的主体内容中主要关注行数据和索引的存储,都位于page的user records部分在一个page内部

  • user records占据page的大部分空间,user records由一条一条的record组成,每条记录代表索引树上的一个节点(非叶子节点和叶子节点)。
  • 单链表的头尾由固定内容的两条记录来表示,字符串形式的"infimum"代表开头,"supremum"代表结尾。这两个用来代表开头结尾的record存储在system records的段里,这个system records和user records是两个平行的段。
  • innodb存在4种不同的record,它们分别是1主键索引树非叶节点 2主键索引树叶子节点 3辅助键索引树非叶节点 4辅助键索引树叶子节点。
    user record在page内以单链表的形式存在,最初数据是按照插入的先后顺序排列的,但是随着新数据的插入和旧数据的删除,数据物理顺序会变得混乱,但他们依然保持着逻辑上的先后顺序。

4-2 常见索引类型(逻辑分类)

  • 主键索引
  • 常规索引(非主键)
  • 唯一索引

复合索引

  • 由多列创建的索引称为符合索引

前缀索引

  • 当索引的字符串列很大时,创建的索引也就变得很大,为了减小索引体积,提高索引的扫描速度,就用索引的前部分字串索引,这样索引占用的空间就会大大减少,并且索引的选择性也不会降低很多。而且是对blob和text列进行索引,或者非常长的varchar列,就必须使用前缀索引,因为mysql不允许索引它们的全部长度。

全文索引

  • innodb引擎对fulltext索引的支持是mysql5.6新引入的特性,之前只有myisam引擎支持fulltext索引。对于fulltext索引的内容可以使用match()…against语法进行查询。

mysql 5.6以前版本

  • 仅myisam引擎支持full text索引,可以使用match()...against语法进行查询
  • 只支持英文。缘由是他从来都使用空格来作为分词的分隔符,而对于中文来讲,显然用空格就不合适,需要针对中文语义进行分词。

mysql 5.6

  • innodb支持full text索引

mysql 5.7以后版本

  • myisam、innodb均支持full text索引
  • 支持中文索引

外键索引

  • 它可以提高查询效率,外键会自动和对应的其他表的主键关联。外键的主要作用是保证记录的一致性和完整性。
  • 不推荐使用外键,在应用层使用外键的思想来保证数据的一致性和完整性。

覆盖索引

  • 使用覆盖索引扫描的查询可以直接使用节点页中的主键值。(具体参照下图中的索引数据结构)

4-3 索引类型-叶子节点数据分类

聚簇索引

  • 叶子节点中存放的是索引和数据记录行
  • 聚簇索引的每一个叶子节点都包含了主键值、事务id、用于事务和mvcc(多版本控制)的回滚指针以及所有的剩余列。

优点

  • 提高数据访问性能:聚簇索引把索引和数据都保存到同一棵b+树数据结构中,并且同时将索引列与相关数据行保存在一起。这意味着,当你访问同一数据页不同行记录时,已经把页加载到了buffer中,再次访问的时候,会在内存中完成访问,不必访问磁盘。
  • 不同于myisam引擎(非聚簇索引),它将索引和数据没有放在一块,放在不同的物理文件中,索引文件是缓存在key_buffer中,索引对应的是磁盘位置,不得不通过磁盘位置访问磁盘数据。

缺点

基于聚簇索引的表在插入新行,或者主键被更新,导致需要移动行的时候,可能面临“页分裂(page split)”的问题。页分裂会导致表占用更多的磁盘空间。
  • 建议在大量插入新行后,选在负载较低的时间段,通过optimize table优化表,因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片
表如果使用uuid作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫面更新
  • 建议使用int的auto_increment作为主键
  • 如果主键比较大的话,那辅助索引将会变的更大,因为辅助索引的叶子存储的是主键值;过长的主键值,会导致非叶子节点占用更多的物理空间

非聚簇索引(辅助索引)

  • 叶子节点中存放的是索引以及对应的数据记录指针(或主键)

4-4 索引扫描方式

紧凑索引扫描(dense index)

  • 为了定位数据需要做权表扫描,为了提高扫描速度,把索引键值单独放在独立的数据的数据块里,并且每个键值都有个指向原数据块的指针,因为索引比较小,扫描索引的速度就比扫描全表快,这种需要扫描所有键值的方式就称为紧凑索引扫描

松散索引扫描(sparse index)

  • 为了提高紧凑索引扫描效率,通过把索引排序和查找算法(b+trre),发现只需要和每个数据块的第一行键值匹配,就可以判断下一个数据块的位置或方向,因此有效数据就是每个数据块的第一行数据,如果把每个数据块的第一行数据创建索引,这样在这个新创建的索引上折半查找,数据定位速度将更快。这种索引扫描方式就称为松散索引扫描。

覆盖索引扫描(covering index)

  • 包含所有满足查询需要的数据的索引称为覆盖索引,即利用索引返回select列表中的字段,而不必根据索引再次读取数据文件
  • 针对某种sql覆盖掉了某种操作

4-5 innodb

一级索引/主键索引/聚簇索引

  • 数据结构
  • 聚簇索引中的每个叶子节点包含主键值、事务id、回滚指针(rollback pointer用于事务和mvcc)和余下的列(如col2)。

innodb默认创建聚簇索引

    1. 有主键时,根据主键创建聚簇索引
    1. 没有主键时,会用一个唯一且不为空的索引列做为主键,成为此表的聚簇索引
    1. 如果以上两个都不满足那innodb自己创建一个虚拟的聚集索引

二级索引/非主键索引/非聚簇索引

  • 数据结构
  • innodb的二级索引(非聚簇索引)的叶子节点中存储的不是“行指针”,而是主键值。
  • 故通过二级索引查找数据时,会进行两次索引查找。存储引擎需要先查找二级索引的叶子节点来获得对应的主键值,然后根据这个主键值到聚簇索引中查找对应的数据行。

4-6 myisam

同innodb数据结构比较

  • 分支主题
  • myisam的二级索引叶子节点存放的还是列值与行号的组合,叶子节点中保存的是数据的物理地址。
  • myisam的主键索引和二级索引没有任何区别,主键索引仅仅只是一个叫做primary的唯一、非空的索引,且myisam引擎中可以不设主键。

5 mvcc

多版本一致性控制

5-1 隐含字段

  • db_trx_id 6字节
    事务id,随事务增加
  • db_roll_ptr 7字节
    回滚指针
  • db_row_id
    隐藏的id,随记录自然增长

5-2 过程

select

  • 当前session 事务版本号>=db_trx_id
  • delete_id is null or 当前事务号

insert

  • 新插入的行,db_trx_id=当前事务号

update

  • 更新行的时候,innodb会把原来行复制一份,db_trx_id=当前事务号

delete

  • 设置删除版本号为当前事务id (db_trx_id=当前事务号??) delete_id = 当前事务号
  • 相当于标记为删除,而不是物理的删除。真是的删除是在innodb的purge线程去做的。

commit

  • 修改事务状态为commit,在 redo log 中写入 commit 记录

rollback

  • 根据当前回滚指针从undo log中找出事务修改前的版本,并恢复

6 事务的实现

6-1 相关概念

  • redo log

binlog

  • 主要用于主从复制
  • undo log 存在表空间中,会有purge线程清理
  • rollback segment
  • 隔离级别

6-2 事务执行过程

  • 用排他锁锁定该行
  • 记录 undo/redo log,确保日志刷到磁盘上持久存储。
  • 更新数据记录,缓存操作并异步刷盘。
  • 将事务日志持久化到 binlog。
  • 回滚事务:根据当前回滚指针从undo log中找出事务修改前的版本,并恢复
  • 提交事务,在 redo log 中写入commit记录。
  • 释放锁资源
  • 异步清理undo段信息
  • 清理保存点列表

6-3 恢复

  • 通过 redo log 来重做事务或通过 undo log 来回滚
  • 以binlog 来确定是提交还是回滚

7 行锁实现

7-1 快照读

snapshot read

  • 定义:读取记录的可见版本(可能是历史版本),不加锁

表现

  • 普通 select

7-2 当前读

current read

  • 定义:读取记录最新版本,会加锁保证不并发修改

表现

  • 特殊查询 select .. where ? for update 加s锁
  • select .. where ? lock in share mode 加x锁
  • insert,update,delete等 dml操作 加x锁

7-3 特点

  • 针对当前读,innodb交互是一条一条的进行,加锁也是一条一条

7-4 过程

  • 发送一个update语句
  • 使用当前读取出第一条并加锁
  • mysql 收到会再发一个update请求,更新这条记录
  • 完成后再读下一条重复上述过程

7-5 两阶段锁实现

  • 2pl就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁
  • 过程

8 锁机制

8-1 锁信息查看

  • show status like 'innodb_row_lock%';

8-2 无锁

  • 普通select 不加任何锁,因此行锁无阻塞,但page锁和表锁会阻塞

8-3 共享锁(读写锁,s锁)

共享读,ix,x锁会阻塞

  • select * from table where ? lock in share mode

8-4 当前读加排他锁(写锁,x锁)

排他锁即其他任何锁都会阻塞

  • select * from table where ? for update
  • insert,update,delete

8-5 gap间隙锁

  • 范围条件的行锁会有
  • 或=条件但数据不存在
  • 意向共享锁
  • 意向排他锁

8-6 范围

表锁

  • 没有用到索引查询时

行级锁

  • 通过索引+mvcc 实现

8-7 replace into

  • 导致主从数据不一致
  • 丢失更新问题

9 隔离级别

  • 丢失更新,脏读,可重复读(数据变化),幻读(行数变化)
  • 读未提交(read uncommited) yes,yes,yes

9-1 读提交(read commited) no,yes,yes

  • 大部分数据库的默认值
  • 一个事务内,数据会变,数据数量也会变
  • 针对当前读:加记录锁,可幻读

9-2 可重复读(repeatable read)no,no,yes

  • mysql默认值,含义是限制其他事务写(排他锁,写锁)
  • 一个事务内数据,数据不变但数量会变
  • 解决幻读,要加范围锁或表锁
  • 当前读:加记录锁,范围锁,不能插入(间隙锁),无幻读

9-3 串行(serializable)no,no,no

  • 从mvcc 退化到基于锁的并发控制,全是当前读,读+读锁,写+写锁

10 mysql 优化

10-1 字段优化

1.更小的最好(相同类型)

  • 选择一个你认为的默认不会超过的类型的下限

2.简单的越好(不同类型)

  • int比string好

3.避免null

  • null的索引以及比较更耗时

10-2 范式优化

  • 采用范式与非范式的结合,如采用一些赘余避免不必要的查询,一个字段包含多个含义
  • 1.所有字段值都是不可分解的原子值
  • 2.数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关
  • 3.每一列数据都和主键直接相关,而不能间接相关

10-3 缓存与汇总表

  • 1.redis缓存
  • 2.汇总访问次数,每次访问的时候都修改,如果汇总一个月的,数据量太大
  • 计数器表
  • 高并发时,计数器表100行,随机访问一行,减少冲突,提高并发处理能力

10-4 join优化

  • 确保on和using字句有索引,考虑关联顺序
  • 索引where和排序冲突的时候,改成关联表方式或者子查询
  • mysql5.6之前的一般需要改子查询为关联表
  • 走索引不一定都快,区分度不够的索引可用考虑用复合索引
  • 多值 max,改为group后max
  • join 时要考虑类型一致,char型要考虑字符集一致,时间类型没走索引,要注意隐世转换
  • select in 会被改写为exists,造成查询缓慢,可改成关联join查询
  • 驱动表的选择,认清关联字段索引,可用straight_join验证猜想
  • query cache并不适合更新,插入,删除非常大的应用
  • group by,order by 只涉及表中一个列
  • order by +limit 将limit,order by内嵌,限制join数量和保证index排序
  • join时 尽量将条件内嵌,先过滤后join。
  • 知道仅有一条时,用limit 1 限制
  • not in或not exits 可转换为外连接或等值连接
  • 某些情况下 or 可用用union all 或union 代替

10-5 排序优化

  • 单表查询,若file sort 则可以用强制使用索引排序,或者用自关联方式
  • 需要where+sort 或者多个sort 做联合索引,并考虑加上排序
  • sort buffer
  • 与group by 连用时 用order by null 禁用排序
  • 别名 若和字段重复也会影响排序优化
  • 索引长度导致不能优化

相关参数

  • a. sort_buffer_size 排序缓存。
  • b. read_rnd_buffer_size 第二次排序缓存。
  • c. max_length_for_sort_data 带普通列的最大排序约束。

file sort过程-普通&双路排序

  • 取出字段a和每行的物理id(tid),然后在sort_buffer_size 中排序
  • 根据排序好的tid从磁盘中拿到行数据,放到read_rnd_buffer_size中

file sort过程-冗余单路排序

  • 区别是第一次是拿到所有相关数据而不只是tid,所以不用二次去磁盘取

file sort 优化

  • 增加相关参数大小
  • 增强tempdir 指向的文件系统io

10-6 limit优化

  • 当与order by 字段联合使用时,若排序字段有重复,需要增加排序列,否则数据会错

10-7 索引问题

复合索引

  • 最常用的放在最前面,无where顺序无关

10-8 常规优化

explain

select_type

  • simple,没有union或子查询
  • primary,嵌套中最外层
    [ˈpraɪmeri]
  • union/union result/dependent union/dependent union
  • derived 衍生,导出,子查询产生临时表
    [di`raivd]
  • subquery 子查询第一个select语句

type

  • const,最多一条,system 特例仅有一条
  • eq_ref,一种索引访问,仅返回1条,且索引为unique,primary
  • ref,返回多条,索引的是最左前缀,可以用于=<>
  • ref_or_null, 针对null
index_merger,索引合并优化
  • 5.1以后支持的,一个表可以使用多个索引
  • index intersect
  • unique_subquery,替换in子查询
  • index_subquery 替换in子查询
  • range,用index检索范围的行
  • index,全扫描索引
  • all

extra

  • using filesort ,没有用索引次序读到排序
  • using temporary
  • not exists
  • using index
  • using index condition
  • using where

possible_keys

  • key 实际使用的索引
  • key_len,key的长度
  • ref 使用哪个列与key一起选择行
  • profile

11 mysql锁机制

1-1 innodb

  • 常用

行级锁

特征

  • 锁定粒度小
  • 锁获取释放复杂度高
  • 并发度高
  • 易出现死锁

分类

行级别
  • 共享锁
  • 排他锁
列级别
  • 意向共享锁
  • 意向排它锁

优化

  • 查询尽量用索引,防止锁升级为表锁
  • 控制事务大小(数据量操作)
  • 减少锁定资源数量
  • 减少锁定时间
减少死锁的发生
  • 类似业务模块中,尽可能按照相同的访问顺序来访问
  • 在同一个事务中,尽可能做到一次锁定所需要的所有资源
  • 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率

1-2 myisam

  • 不常用

表级锁

特征

  • 锁定粒度大
  • 锁获取释放简单
  • 并发度低
  • 不易出现死锁
读写互阻塞
  • 读取表的时候,另一线程无法进行读或写
  • 写入表的时候,另一线程无法进行读或写

分类

  • 表级别的锁

优化

  • 缩短锁定时间
  • 分离并行的操作

你可能感兴趣的:(mySQL技术的方方面面,不管是应用还是面试,看这一文就够了)