面试八股-数据库

1.MySQL原理

1.1.事务

1.1.1.事务基本特性ACID

  • A 原子性,要么全成功,要么全失败 由undo log日志保证,其记录需要回滚日志信息,回滚就撤销执行成功的sql。

  • C 一致性,事务不提交,就不会应用到数据库 一般由代码层面和其他三大特性共同保证。

  • I 隔离性,一个事务修改最终提交前,其他事务不可见 事务之间不相互影响,由锁和MVCC保证

  • D 持久化,一旦提交,修改永久存储到数据库 由内存+ redo log保证,修改数据同时在内存和redo log记录,提交通过redo log刷盘,宕机可以从redo log恢复。 redo log是物理操作,undo log是逻辑日志。

1.1.2.事务隔离级别及其MySQL实现

隔离级别

  • 读未提交(read uncommit) 读到其他事务未提交的数据 脏读
  • 读已提交(read commit) oracle默认级别 两次读取结果不一致 不可重复读
  • 可重复读(repeatable read) mysql默认级别 每次读取结果都一样 可能幻读 (MySQL通过Next-key Lock锁算法解决该问题)
  • 串行(serializable) 一般不使用 会给每一行读取数据加锁,导致大量超时和锁竞争问题 可以彻底解决幻读问题

MySQL实现

  • 读未提交和读已提交,通过Record Lock算法实现行锁
  • 可重复读,使用Next-Key Lock算法实现了行锁
  • 串行,每个读操作自动加共享锁

1.1.3.事务分类

  • 扁平事务:BEGIN开始 COMMIT或者ROLLBACK结束
  • 带保存点的事务:有savepoint,可以回滚到之前一个状态
  • 链事务:提交事务时释放不需要的数据对象,将必要上下文传给下一个要开始的事务
  • 嵌套事务:层次结构框架,顶层事务控制各个层次事务
  • 分布式事务:多个节点需要满足ACID

1.1.4.MySQL事务回滚

  • ROLLBACK 结束用户所有事务,并且撤销所有未提交更改
  • ROLLBACK TO [SAVEPOINT] identifier 配合SAVEPOINT命令一同使用,回滚到之前某个标记点

1.2.范式

  • 第一范式:所有字段都是不可再分解的原子值
  • 第二范式:属性完全依赖于主键,和主键相关,不能有无关的列
  • 第三范式:每一列都和主键直接相关,不能间接相关(推导)

1.3.索引

  • 唯一性索引保证数据唯一性
  • 加快数据检索速率和表连接效率

1.3.1.索引分类

  • 数据结构分类:
    • Hash索引 适合随机查找,不适合排序、顺序查找,存在冲突影响效率,适合大规模查找单个记录。
    • b树和b+树 适合范围查找
    • 全文索引 倒排索引表实现,适合大量文本中检索
  • 数据索引情况分类:
    • 聚簇:数据和索引放在一起,数据物理存放顺序和索引顺序一致。单个和范围查询效率高,维护代价大,插入分页会引起索引分裂。
    • 非聚簇:叶子节点不存储数据,只存储地址,之后需要根据地址找数据。维护代价低,不容易分裂,内存较小,对于count等操作效率高。

1.3.2.索引相关数据结构问题

  • 红黑树 :弱平衡树,高度略高于强平衡树,但是插入操作旋转次数少,每次最多旋转三次就可以达到要求的大致平衡。
  • B树和B+树对比
    • B树每个叶子存储key和data,B+树只有叶子存储data,B+树可以保证非叶子节点存储更多的key,比B树更加矮胖,效率更快。
    • B+树每个叶子节点保存指向相邻节点的指针,可以支持全表扫描,范围查找等。
  • 使用B+树的优势
    • 磁盘读写代价更低,因为内部节点只有key,尺寸更小,一次可以读入更多关键字,减少读入次数。
    • 查询效率稳定,每次都必须到叶子才能获取数据,查询效率相同。
    • 可以通过叶子节点进行全表遍历。

1.3.3.索引分裂

9-1分裂和5-5分裂。

  • 5-5: 并发较高,索引无序状态
  • 9-1: 并发较低,索引有序

1.3.4.索引使用原则

  • 适合建立索引
    • where中的列和连接语句中的列
  • 不适合建立索引
    • 基数小的表
    • 更新频繁的列
    • 过多建立索引
    • 重复度高、辨识度低的,比如性别
    • Text,image bit等类型
  • 需要酌情考虑的
    • 尽可能建立短索引,如果要长的可以考虑前缀长度

1.3.5.索引失效

  • where中使用计算函数
  • 大于等于号
  • or这样的条件判断有一个字段没走索引
  • like的通配符在左边
  • 数据过少发现扫描更快
  • 组合索引失效(没有最左原则)
  • 隐式转换,比如字段为字符串,但是筛选使用了数值

1.3.6.索引重建

时机

  1. 表频繁update、delete

  2. 发生了alter table… move操作

如何判断

  1. 分析索引结构 analyze index … valid structure;
  2. 查询index_stats表select height,DEL_LF_ROWS/LF_ROWS from index_stats; height>=4或者比>0.2时要考虑重建

方式

  1. drop原索引,创建新的
  2. 直接重建 alter index xxx rebuild;alter index xxx rebuild online; 后者会减少重建带来的任何加锁问题,新旧索引会同时存在,结束之后drop原来表,不会阻塞DML操作。会产生大量Redo Log

1.4.锁

1.4.1.分类

  • 属性
    • 共享锁 读锁 只能读,不能写,避免不可重复读
    • 排他锁 写锁,加上之后不能加其他锁,避免脏读
  • 粒度
    • 表锁 粒度大加锁简单容易冲突
    • 页锁 介于行锁表锁之间,一次锁定一页记录
    • 行锁 粒度小,加锁麻烦不容易冲突,但可能死锁,实现方式通过索引
      • 记录锁 锁住一条记录,命中条件是唯一索引
      • 间隙锁 锁住区间,禁止插入
      • 临键锁Nexk-key 把记录区间都锁住,左开右闭,防止幻读出现
  • 状态
    • 意向共享锁和意向排他锁 为了简化加表锁之前要查看是否有行锁的操作,获取到意向锁才能给表加锁。
  • 加锁开销
    • 悲观锁:频繁写场景,直接独占资源,防止频繁重试,数据库一般提供悲观锁。
    • 乐观锁:不频繁写场景,增加并发行,减少锁开销,通常由CAS和版本号控制实现。CAS:地址,预期旧值,新值,只有内存值和旧值相同才更新新值,可能出现aba问题,循环消耗CPU。可以使用版本号机制解决aba问题。

幻读解决:Next-Key: 记录锁 + 间隙锁

1.4.2.加锁

使用:

  • SELECT … LOCK IN SHARE MODE 加共享锁
  • SELECT … FOR UPDATE 加排他锁
  • INSERT/UPDATE/DELETE 加排他锁
START TRANSACTION;	# 开始事务
SELECT xxx FROM xxx FOR UPDATE; # 获得锁
UPDATE ...
COMMIT; # 释放锁

加锁可能性

  • 主键索引命中,直接加行锁
  • 主键索引没有命中,RC隔离等级下不加锁,RR等级下在前后两个索引加间隙锁
  • 二级唯一索引命中,加行锁
  • 二级唯一索引没有命中,RC等级不加锁,RR等级下加间隙锁
  • 二级非唯一索引命中,在RC等级下加行锁,RR等级下还需要加上三个间隙锁
  • 二级非唯一索引不命中,RC下不加所,RR下加间隙锁
  • 没有索引 RC下加表锁,RR下给记录加锁并且加间隙锁形成Next-Key键

1.4.3.死锁处理

  1. 超时
  2. 等待图

1.5.引擎

InnoDB MyISAM
事务 支持事务、回滚、崩溃修复、事务安全,通过MVCC支持高并发事务 不支持事务但是性能较高
外键 支持 不支持
索引 主键是聚簇索引,其他是非聚簇索引 均为非聚簇索引,内存效率更高
保存具体行数 没有 有,查询方便
操作粒度 行锁 表锁 并发受限
场景 插入频繁 大量查询少量插入,数据仓库
data域存储实质 数据地址 辅助索引是主键值,所以最好使用短主键

1.5.1.InnoDB实现事务/update流程

引入了buffer pool,操作先在buffer pool实现,然后写入磁盘。

  1. InnoDB收到update语句之后,先根据条件找到数据所在页然后缓存在buffer pool中
  2. 执行语句,把语句改写为物理操作形式,存在redo log中(记录页修改),然后修改buffer pool中的数据,保证了持久性
  3. 生成undo log日志,用于回滚
  4. 如果事务提交,持久化redo log,把缓存数据写入磁盘
  5. 如果事务回滚,利用undo日志回滚

1.5.2.InnoDB逻辑存储结构

  • 表空间:InnoDB存储引擎逻辑结构最高层,一个idb文件。

  • 段:表空间由各个段组成,如数据段,索引段回滚段等,段是逻辑概念,创建一个索引会创建两个段,索引段(非叶子节点)和数据段(叶子节点),表的段数为索引数*2。

  • 区:一个段由多个区组成,每个段至少有一个区,一个段管理的空间大小无限,但是最小单位就是区,一个区空间为1M,可以放64页。有点像操作系统的段页式。

  • 页:一页为16kb,存放一个节点的数据,也是磁盘管理的最小单位,(一个B+树节点,页编号可以映射到物理文件偏移)

  • 行:一个记录

  • 字段:一个记录里面的一个字段

1.6.MVCC

多版本并发控制,MVCC使得数据库读不会对数据库加锁,提高了其并发处理能力。

  1. 快照读不加锁,查询当前版本,所以不会幻读
  2. 当前读加锁,即for update时,读时加锁锁住范围记录和间隙,避免幻读现象

只在读已提交和可重复读两个级别使用。

原理:聚簇索引中有两个隐藏的列,一个用来记录对数据做出修改的事务id,另一个指向上一个版本的位置。

  1. ReadView维护事务的id,用数组的方式排序存储
  2. 访问某条数据回根据数据上的事务id找最新版本
  3. 如果id比readview的id都小,说明该事务已经提交了,可以访问
  4. 如果id比readview的id都大,不能访问
  5. 如果在区间内,则判断是否在表中,是的话说明事务还没有提交,否则证明已提交可以访问。
  6. 通过版本链回溯找到可以访问的版本为止。

不可重复读下每次查询都会创建新的readview,所以可能会造成不一致。

可重复读下使用同一个readview,所以两次结果相同,实现读时不允许写。

2.SQL相关基础知识

2.1.SQL执行过程

  1. 连接器:通过连接器进行连接和权限验证
  2. 查询缓存:如果命中缓存直接返回结果,实际应用效率比较低,所以一般不用
  3. 分析器:词法分析和语法分析
  4. 优化器:去确定如何更高效率执行,确定索引、表连接顺序等
  5. 执行器:执行查询,返回结果

2.2.事务执行流程

引入了buffer pool,操作先在buffer pool实现,然后写入磁盘。

  1. InnoDB收到update语句之后,先根据条件找到数据所在页然后缓存在buffer pool中
  2. 执行语句,把语句改写为物理操作形式,存在redo log中(记录页修改),然后修改buffer pool中的数据,保证了持久性
  3. 生成undo log日志,用于回滚
  4. 如果事务提交,持久化redo log,把缓存数据写入磁盘
  5. 如果事务回滚,利用undo日志回滚

2.3.DDL SQL原理

mysql在线ddl 加字段 加索引等的操作

  1. 对表加锁 此时表只读
  2. 复制原表物理结构
  3. 修改表物理结构
  4. 原表数据导入中间表,同步完成之后,锁定中间表,删除原表
  5. 重命名中间表为原表
  6. 刷新数据字典,释放锁

整个过程会锁表,造成当前表无法写入数据,数据越大等待时间越长,卡死在那里(拒绝执行update和insert操作,表现就是延迟一直在等)

2.4.SQL行转列

有点像Excel的一维表转化为二维表

两种方法都需要配合SUM函数,实质是筛选出不同的列的条件求和,不满足的为0。

  1. 使用CASE…WHEN…THEN语句实现
  2. 使用IF函数实现

2.5.关联更新

UPDATE…FROM…

update b set b.col=a.col from a,b where a.id=b.id;
update b set col=a.col from b inner join a on a.id=b.id;
update b set b.col=a.col from b left Join a on b.id = a.id;

2.6.建立索引语句

2.6.1.创建表时建立

CREATE TABLE t1 (  id INT NOT NULL,     name CHAR(30) NOT NULL,     UNIQUE INDEX UniqIdx(id) );

2.6.2.在已经存在的表中建立

ALTER TABLE book ADD UNIQUE INDEX UniqidIdx (bookId);
CREATE UNIQUE INDEX UniqidIdx ON book (bookId);

3.SQL优化

3.1.整体思路

首先定位性能瓶颈然后再想办法解决。

  1. 通过慢日志查询发现执行效率低的语句。
  2. 对执行效率低的语句使用explain命令查看其执行计划。
  3. 查看走了哪个索引,如何命中,扫描多少行,返回值和读取行数百分比等。

3.2.慢日志开启

MySQL中慢查询日志默认是关闭的,可以通过配置文件my.ini或者my.cnf中的log-slow-queries选项打开,也可以在MySQL服务启动的时候使用–log-slow-queries[=file_name]启动慢查询日志。

启动慢查询日志时,需要在my.ini或者my.cnf文件中配置long_query_time选项指定记录阈值,如果某条查询语句的查询时间超过了这个值,这个查询过程将被记录到慢查询日志文件中。分析慢查询日志,

3.3.explain字段解释

explain字段解释:

  • select_type 查询类型
    • simple 简单查询,不包括连接和子查询
    • primary 复杂查询,包括复杂子查询
  • Table 设计哪些表,单个表还是Union还是子查询等
  • Type 查询到数据的方式,索引一次命中,非唯一索引扫描命中,全表扫描等 *
    • system 表中只有一行记录
    • const 一次命中
    • eq_ref 唯一性索引扫描,只有一条记录匹配
    • ref 非唯一性索引扫描
    • all 全表扫描,效率最低
  • Possible_keys 可能用到的索引
  • key 走的索引 *
  • Key_len 索引字段长,一般越短越好 *
  • Rows 按照计划需要扫描多少行 *
  • Filter 读取行数和返回值百分比,越高越好,证明读取的行数越有意义
  • Extral 额外信息 *
    • 用到了临时表
    • file sort排序没用到索引

需要优化的情况和措施

  • rows过多,看where能不能过滤更多条件
  • key为null,考虑创建索引
  • 有多个待选索引,实际索引走错,用force index强制走预期索引
  • extral字段出现了信息

3.4.SQL执行很慢的原因

3.4.1.偶尔很慢,不是SQL本身的原因

  1. 数据库在刷新脏页,数据库将更新写入redo log,然后刷新磁盘,如果redo log写满,那么会先刷新磁盘,导致满。内存不够也可能出现。
  2. 拿不到锁,可以使用show processlist命令查看撞田,看是否等待锁

3.4.2.一直很慢,SQL本身的原因

  1. 没有用索引,或者语句涉及了全表扫描
  2. 数据库选错了索引,数据库会根据行数判断是否用索引,如果判断失误就会扫描,可以用force index命令执行索引
  3. SQL写的有问题,需要优化

3.5.优化思路

3.5.1.针对数据库结构

  • 把低频率字段拆出来建立新表
  • 增加冗余字段

3.5.2.针对插入

  • MyISAM禁用索引

    • 禁用唯一性检查
    • 使用批量插入
    • 使用LOAD DATA INFILE语句批量导入
  • InnoDB

    • 禁用唯一性检查 set unique_checks=0
    • 禁用外键检查
    • 禁用自动提交

3.5.3.优化子查询

  • 分解关联查询,在应用程序中连接
  • 连接代替子查询

3.5.4.尽可能走索引

  • 让索引起作用 LIKE 左匹配 OR
  • 优化LIMIT分页,使用覆盖索引

3.5.5.SQL语句

  • 避免SELECT *

  • where中过滤不必要列

  • where中不要用比较操作,会全表扫描

  • exists代替in,后者会笛卡尔积再筛选,前者对每条记录进行条件判断

  • 用where代替having,having先操作再过滤

4.大表优化

当MySQL单表记录数据过大(业界500万行),性能会下降,所以需要采取措施,尽可能不拆表,拆分会比较麻烦。

4.1.设计措施

  1. 字段设计灵活 足够存放即可,float代替double,varchar代替char
  2. 索引设计 给查询频繁、连接频繁的列加索引,缩短索引长度
  3. 读写分离
  4. 拆分,包括水平拆分和垂直拆分
  5. 查询优化 限定查询范围 where中少用函数 大于小于号 or等 exist代替in,少用having

4.2.拆分

一般先垂直再水平

垂直:

微服务拆分,一般已经坐到垂直分库了

对于库内字段多的表,将不常用,数据大的再进行拆分

水平则需要根据业务场景来决定用什么字段,比如日单1000万,大部分场景是C端,使用user_id作为sharding_key,数据支持查到最近3个月订单,超过3个月归档,3个月就是9亿,分1024张,每张1000万左右。如对id进行hash然后对1024取模,落到对应表。

4.3.ID唯一性

  1. 设定步长,每个表设定不同的步长,主键落到不同表不会冲突。

  2. 分布式ID,雪花算法或者自己实现

  3. 分表后不使用主键作为查询依据,每个表新增加一个字段作为唯一主键使用,比如订单表订单号唯一的,不管在哪张表都可用。

4.4.分表后非sharding_key的查询怎么处理

  1. 做一个mapping表,比如商家可能会查询订单列表,那么可以保存商家和用户的关系,查询的时候先通过商家查询到用户列表,在通过user_id去查询。
  2. 打宽表,一般而言,商户端对数据实时性要求不高,比如查询订单列表,可以先同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
  3. 数据量不大的话,比如后台查询等,可以多线程扫表,然后聚合结果,或者异步形式来做。

4.5.多实例主从同步

4.5.1.流程

涉及主节点的一个线程(binlog dump)和从节点的两个线程(IO,SQL)

  1. 主节点的binlog保存数据库修改,有变动时binlog dump将binlog发送给从节点
  2. 从节点IO线程接受binlog内容,写入relay log中
  3. 从节点SQL线程读取relay log并更新数据

4.5.2.复制模式

  1. 记录过程,将更改数据的sql写入bin log,不用记录数据,减少binlog数据量,但是某些操作可能会使数据不一致
  2. 记录更改行以及该行新数据,bin log会大量增加,但是不会不一致
  3. 混合模式,一般使用第一种记录sql的方式,如果无法处理,使用第二种模式

4.5.3.通信方式

  1. 异步方式 mysql默认,发送binlog dump就不管了,可能造成数据丢失
  2. 同步方式 强制所有从库都成功之后同步通知主库
  3. 半同步方式 一个节点成功即可

5.Mybatis

DAO(Data Access Object)数据访问接口。

Mybatis 优秀持久层ORM框架,建立实体和SQL的映射关系,半自动化的ORM实现,使用Mybatis可以解决驱动注册、connection获取等,可以使开发人员关注业务sql本身。

5.1.优缺点

优点:

  • 和JDBC比减少了50%的代码量,自动管理数据库连接
  • 接触SQL和程序代码的耦合,将业务逻辑和数据访问分离,存在xml中更好维护
  • 提供映射标签,支持对象和数据库ORM字段关系映射
  • 可以和Spring很好的集成

缺点

  • SQL编写工作量大
  • SQL依赖于数据库,不易移植

5.2.和Hibernate对比

  • 自动化和半自动化:Hiberate完全封装,可以完全操作对象不关心SQL,可以减少更多代码量,Mybatis为半自动
  • 可移植性:Mybatis手写SQL依赖于数据库,移植困难,Hibernate更加方便移植
  • 灵活性:Mybatis在SQL优化和修改上更加灵活

5.3.整体原理

  • 读取配置文件(mapper,包含数据库配置和具体的实体xml文件),构建sqlSessionFactory,即会话工厂。
  • 通过会话工厂,创建sqlSession会话,mybatis通过sqlsession来操作数据库。

5.4.xml文件细节

  • sql,表示是sql语句,可以在其他语句中引用

  • parameterType 指定参数类型,可以指定基本数据类型、包装类型以及自己编写的JavaBean

  • resultType 指定返回信息对应的Java数据类型

  • #{}预处理语句,没法模糊查询,${}直接拼接,可以模糊查询,但是可能SQL注入

  • sql片段标签,如where,if,foreach set trim choose when otherwise bind,可以通过include引入

dao层的方法不能直接在java文件中重载,因为xml中id不能重复,可以通过动态sql来实现重载,比如判断是否为空等。

6.非关系型数据库

6.1.CAP

  • 一致性 分布式系统数据一致
  • 可用性 一直可以做正常读写操作
  • 分区容错性 某些节点故障不影响整体可用

三选二

  • CA 不再是分布式系统
  • CP 牺牲用户体验处理一致性问题
  • AP 不追求强一致性

6.2.分类

  • 高性能并发读写的k-v数据库:Redis
  • 面向海亮数据访问的文档数据库:MongoDB

6.3.和关系性数据库比较

非关系型 关系型
数据一致性 CAP ACID一致性高
效率 并发读写很高 较低
数据存储结构 存储结构多种 固定表结构
可扩展性 扩展性较强,因为数据结构灵活 扩展性较弱,数据结构不易变动

你可能感兴趣的:(面试八股,数据库,面试,mysql)