个人(关系型)数据库面试八股

129 阅读11分钟

MySQL

什么是存储引擎

存储数据、建立索引、更新/查询数据等技术的实现方式

基于表而不是基于库

InnoDB

特点:事务、行级锁、外键

表空间文件: .ibd,存储表结构、数据、索引

逻辑存储结构:

  • 表空间
  • 区(1M)
  • 页(索引页、数据页,16K)
  • 行(最后一次操作的事务、行数据)

场景:对事务的完整性要求比较高、在并发条件下要求数据的一致性

MyISAM

特点:不支持事务、不支持外键、支持表锁不支持行锁、访问速度快,支持R树索引

表空间文件:

  • .sdi: 表结构数据
  • .MYD: 数据
  • .MYI: 索引

场景:以读入和插入操作为主,如业务系统中的日志、电商中的足迹,但是都被Mogodb替代了

MEMORY

由于放在内存,仅能作为临时表或缓存使用

特点:内存存放、也hash索引

表空间文件: .sdi, 仅存储表结构数据

场景:临时表或者缓存,不能太大,但是都被Redis替代了

索引

高效获取数据的数据结构(有序)

优点:提高数据检索效率,降低数据库IO成本

缺点:降低了更新表(如插入、更新、删除)的效率

索引结构
    B+树索引(大部分引擎都支持)
    Hash索引(不支持范围查询以及排序,MEMORY中有,InnoDB自适应hash)
    R树索引(空间索引,用于地理空间数据类型,MyISAM中有)
    Full-text(全文索引,建立倒排索引快速匹配文档,ES中有应用,InnoDB、MyISAM都支持)
B树结构(需要存数据导致一页存储的指针变少,层级变高)
B+树结构(每个页存储一个节点,指针固定占用6字节,可以用来计算树高)
Hash索引结构

索引基本分类
    主键索引(默认自动创建,仅一个)
    唯一索引(UNIQUE关键字创建)
    常规索引
    全文索引(不需要了解,查找的是文本的关键词,不是索引中的值)
索引分类(按照存储形式)
    聚集索引(叶子结点保存整个行数据,必须有,而且只有一个)
        选取主键索引 or 第一个唯一索引 or 生成一个rowid
    二级索引(叶子结点存储对应的主键,可以有多个,回表查询)

性能分析

SQL执行频率:看增删改查每一类SQL语句频次

慢查询日志:记录超过10秒的SQL语句, 默认没开

profile详情:每一条语句的执行耗时是多少以及耗时在哪里,默认关闭

explain执行计划:执行过程中是否用到索引、表的连接情况、表的连接顺序,使用EXPLAIN

索引使用规则

失效情况(whele部分)

最左前缀法则

范围查询(<、>右侧列会失效,>=、<=可以)导致右侧列索引失效

索引列运算,会导致索引失效

字符串不加引号,索引失效

模糊查询,头部模糊会导致索引失效,尾部模糊不会失效

or分割的条件,只有两侧都有索引才能走索引,否则索引失效

统计评估使用索引比全表慢,则不使用索引(比如value>=minVal的查询,整张表都满足)

SQL提示
- use index, 仅建议,不强制
- ignore index
- force index, 强制

高效情况(返回的字段)

使用覆盖索引(需要返回的列,在该索引中全部能找到,不需要回表查询)
    注意:在二级索引中,data部分仅存id,但是索引节点上的索引列(包括联合索引)也可以作为返回值

前缀索引:应对长文本情况

联合索引(推荐,应考虑顺序,最左前缀法则)

SQL优化

插入数据
    批量插入
    开启事务
    主键顺序插入
    大批量数据用load
主键插入
    避免页分裂,页合并(删除是假删除)
    降低主键长度(避免二级索引效率过低)
    顺序插入,选择自增主键
    尽量不使用UUID或者其他自然主键(如身份证号)
    避免对主键的修改
order by优化
    通过有序索引直接返回有序数据,不需要额外排序
group by优化
    添加索引
    也满足最左前缀法则
limit优化
    进行limit分页查询时,越往后,分页查询效率越低,因为需要对前面的数据进行排序
    覆盖索引+子查询
count优化
    MYISAM引擎把一个表的总行数存在磁盘,count(*)效率会很高(在没有whele等语句情况下)
    InnoDB需要一行一行读出来然后累计计数
    推荐用redis手动维护计数(插入+1,删除-1)
    推荐count(*),因为引擎做了优化
update
    根据索引字段或者主键更新,因为没有索引的update会从行锁升级为表锁
     

全局锁

场景:全库的逻辑备份

表级锁

表锁
    表共享读锁
    表独占写锁
元数据锁
    访问数据时自动加上
    在表上有活动事务的时候,不可以对元数据进行写入操作
    为了避免DML与DDL冲突,保证读写的正确性
    MDL读锁(共享)
        对一张表进行增删改查时自动加上
    MDL写锁(排他)
        对表结构进行变更时自动加上
意向锁
    避免DML在执行时,加的行锁与表锁的冲突
    表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查
    
    意向共享锁
        与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥
    意向排他锁
        与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥
    

行级锁

在RR事务隔离级别,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读

行锁
    锁定单个行记录,RC、RR隔离级别支持
    共享锁 LOCK IN SHARE MODE
    排他锁 FOR UPDATE
    场景
        针对唯一索引进行检索时,自动优化为行锁
        行锁针对索引加锁,没索引将升级为表锁
间隙锁
    锁定间隙,防止产生幻读现象,RR隔离级别支持
    场景
        索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁
        索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时, 退化为间隙锁
临键锁(next-key)
    行锁和间隙锁的组合,锁住数据和间隙,RR隔离级别支持
    场景
        默认场景
        索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止
    

InnoDB引擎的逻辑存储结构

表空间:.ibd文件。

段:数据段、索引段、回滚段。数据段就是B+树的叶子节点,索引段即非叶子节点。

区:每个区1M。

页:磁盘管理的最小单元,每个页大小16KB,一个区一共有64个连续的页。为了保证连续性,存储引擎每次从磁盘申请4-5个区

行:trx_id、Roll_pointer、col1、col2、col3...

隐藏列Trx_id: 对记录进行修改时,对应的事务id

隐藏列Roll_pointer:对记录进行修改时,指向旧的版本(在undo日志中)

InnoDB内存架构

缓冲池Buffer Pool:空闲页、干净页、脏页

更改缓冲区Change Buffer:如果数据页不在缓冲池,先将数据变更存(DML)到更改缓冲区,数据被读取时再合并到缓冲池中。

日志缓冲区Log Buffer:日志数据(redo log、undo log),每次事务提交刷新/每秒刷新

自适应哈希索引Adaptive Hash Index:如果hash索引可以提升速度,则自动建立hash索引

磁盘结构

系统表空间

存放更改缓冲区change Buffer数据

每张表的单独表空间

每张表的表空间文件.bd

通用表空间

手动指定使用该空间,略

撤销表空间(Undo表空间)

自动创建两个默认的undo表空间文件,用于存储 undo log 日志

双写缓冲区

将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件,便于系统异常时恢复数据

Redo Log

重做日志,用来实现事务的持久性,用于保证异常时数据恢复
双日志文件循环写

后台线程

Master Thread: 负责调度其他线程、缓冲池中的数据异步刷新,刷新脏页到磁盘

IO Thread: 负责AIO请求的回调(读、写、日志缓冲区刷新、更改缓冲区刷新)

Purge Thread: 回收事务已经提交了的undo log

Page Cleaner Thread:协助刷新脏页到磁盘,减轻Master Thread工作压力

事务原理

原子性 | 一致性 | 持久性:redo log、undo log

隔离性:锁、MVCC

Redo log(持久性): 当脏页刷新到磁盘错误时,方便恢复数据。(循环写)

0. 原理:WAL(Write-Ahead Logging)
1. 增删改数据
2. Buffer Pool中产生脏页
3. Reedolog buffer记录数据页变化
4. 事务提交,将Redolog buffer中的数据页变化刷新到磁盘中

undo log(原子性):用于记录数据被修改前的信息,作用是提供回滚(系统中断、死锁)和 MVCC

undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为可能还用于MVCC
    insert事务,产生的undo log日志只在回滚时需要,提交后可以立即删除
    update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即删除

当前读 | 快照读

当前读

读取记录的最新版本,其他并发事务不能修改当前记录,会对读取的记录加锁。
lock in share mode
for update
update
insert
delete

快照读

简单的select(不加锁)就是快照读,读取的是记录数据的可见版本

读已提交:每次select都生成一个快照读
可重复读:开启事务后第一个select语句是快照读
串行化:快照读退化为当前读

MVCC

记录中的三个隐藏字段
    TRX_ID:最近修改事务ID
    ROLL_PTR:指向这条记录的上一个版本
    ROW_ID:隐藏主键(如果表结构没有主键,会生成)
undolog版本连
    不同事务或相同事务对同一条记录进行修改,会导致记录的undolog生成一条记录版本链条
读视图
    核心字段
        当前活跃事务ID集合
        最小活跃事务ID
        预分配事务ID(当前最大事务ID+1,因为是自增的)
        创建者(读视图)的事务ID
    版本链数据访问规则
        当前事务为ID为版本链上的记录中的事务ID
        其他参数是读视图里面的,在快照读的时候已经创建了
        1. 当前事务ID == 创建者事务ID:可以访问该版本,数据是当前这个事务更改的
        2. 当前事务ID < 最小活跃事务ID:可以访问该版本,说明数据已经提交了
        3. 当前事务ID > 最大活跃事务ID:不可访问该版本,说明该事务是在ReadView生成后才开启的
        4. 最小活跃事务ID <= 当前事务ID <= 最大活跃事务ID, 当前事务ID不在活跃事务ID集合中:可以访问,说明已经提交了
    生成时机
        读已提交:每一次执行快照读都会生成读视图
        可重复读:仅在事务中第一次执行快照读时生成读视图,后续复用该读视图
        

日志

二进制日志(BINLOG)

记录了所有的DDL语句与DML语句。默认开启

- 灾难时的数据恢复
- 主从复制

查询日志

查询日志中记录了所有的操作语句,默认不开启

慢查询日志

所有执行时间超过参数(默认10秒)的操作语句

主从复制

将主库的BINLOG同步到从库,然后重新执行,从而使得从库和主库数据保持一致

优点:

  1. 主库出现问题,可以快速切换到从库提供服务
  2. 实现读写分离,降低主库的访问压力
  3. 可以在从库中执行备份,避免备份期间影响主库服务

分库分表

单数据库问题:

  1. IO瓶颈:磁盘IO、网络IO
  2. CPU瓶颈

分库分表的中心思想:

将数据分散存储,使得单一数据库/表的数据量变小来缓解单一数据库的性能 问题,从而达到提升数据库性能的目的。

拆分策略:

  1. 垂直拆分:垂直分库、垂直分表
  2. 水平拆分:水平分库、水平分表

实现技术:

  • shardingJDBC:基于AOP原理,在应用程序中对SQL进行拦截,解析、改写、路由处理。
  • MyCat:分库分表中间件,简单,性能不及前者

读写分离

主库提供写操作,从库提供读操作