数据库常见八股

目录

  1. 1. MISC
    1. 1.1. 数据库范式
      1. 1.1.1. 第一范式(1NF)
      2. 1.1.2. 第二范式(2NF)
      3. 1.1.3. 第三范式(3NF)
  2. 2. SQL数据库
    1. 2.1. 索引
      1. 2.1.1. 种类
      2. 2.1.2. B+树的优点
      3. 2.1.3. 什么情况下需要加B+树索引
      4. 2.1.4. 什么情况使用哈希索引
      5. 2.1.5. 观察索引的效果
    2. 2.2. 隔离级别
    3. 2.3.
      1. 2.3.1. 行锁
        1. 2.3.1.1. 实现
      2. 2.3.2. 表锁
        1. 2.3.2.1. 实现
        2. 2.3.2.2. 实现
      3. 2.3.3. 乐观锁
        1. 2.3.3.1. 情景
        2. 2.3.3.2. 实现方法
        3. 2.3.3.3. 更好的实现方法
      4. 2.3.4. 悲观锁
        1. 2.3.4.1. 情景
        2. 2.3.4.2. 实现方法
      5. 2.3.5. MVCC多版本并发控制
      6. 2.3.6. 当前读和快照读
  3. 3. Redis数据库
    1. 3.1. 数据类型
    2. 3.2. 事务
      1. 3.2.1. 伪事务的实现
      2. 3.2.2. 常用于

整理中

MISC

数据库范式

华为秋招还真问这个

第一范式(1NF)

强调的是 列的原子性 ,即列不能够再分成其他几列。

第二范式(2NF)

首先是 1NF,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。

第三范式(3NF)

在1NF基础上,任何非主属性不依赖于其它非主属性,即 在2NF基础上消除传递依赖

第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。

SQL数据库

索引

索引根据主键生成,可以提供对应表的高效查询。

种类

MySQL提供两种索引:B+树和哈希。

B+树的优点

  • 对数级别的时间复杂度。
  • 可以范围查找-因为B+数的数据均于叶子节点间,并形成链表。

什么情况下需要加B+树索引

  • 读取量大
  • 数据行很多
  • 大部分数据行互不相同
  • 常常需要范围查找

什么情况使用哈希索引

  • 数据行很多
  • 大部分数据行互不相同
  • 常常进行

观察索引的效果

使用explain命令

隔离级别

脏读 不可重复读 幻读
读未提交
读提交 ×
可重复读 × ×
序列化读写 × × ×
  • 读未提交属于最奔放的一种隔离模式,实际上就是没有隔离,会导致脏读。就像是给饭卡充值,刚点开支〇宝输完要充值的钱,还没有输入支付密码,饭卡里面的钱就增加了。
  • 读提交就是只能读到被其它事务修改提交后的数据。然而这也存在一些问题——不可重复读BUG。假设饭卡余额10块,我施展影分身之术,把饭卡分成两份分别去小卖部和食堂同时刷9块钱的东西,那么小卖部和食堂看到我的余额都是10块,我们就假设小卖部的事物先提交,那么后提交事务的食堂在提交的时候如果再查询,就会看到饭卡里面只剩1块了,扣完9元后余额变成了-8元。
  • 可重复读解决了食堂读饭卡金额不对的问题。在小卖部执行这个事务的时候,会生成一个10元余额的只读记录,食堂再来读的时候,即使小卖部已经把自己的事务提交,只要食堂的事务没有结束,食堂无论怎么读都会读到10元,只有在写入新的余额的时候,才会发生冲突,因为此时的余额已经被改变了。
  • 序列化流程上没有任何可能出错的地方,只是性能不是很美丽。

不可重复读和幻读的区别:

  • "不可重复读" 主要关注的是某个数据行的变化。它涉及到在一个事务内两次读取相同数据行时,数据行的内容已经被其他事务修改。
  • "幻读" 则关注的是某个数据表的变化,通常涉及到在一个事务内两次查询之间,其他事务插入了新的数据行,导致查询的结果集发生变化。

行锁

访问数据库的时候,锁定整个行数据,防止并发错误。

实现

//查询商品库存信息
select quantity from items where id=1 for update;
//修改商品库存为2
update items set quantity=2 where id = 1;

表锁

访问数据库的时候,锁定整个表数据,防止并发错误。

实现

//锁定表
lock tables items write;
//查询商品库存信息
select quantity from items where id=1;
//修改商品库存为2
update items set quantity=2 where id = 1;
//解锁表
unlock tables;

实现

乐观锁

乐观锁在读取的时候会认为其它人不会修改读取的数据,只有读取并提交后才会对提交的数据进行冲突验证,不会死锁。

情景

读多写少,预期的并发数不高

实现方法

使用版本号或时间戳来实现,并非实际在数据库中加锁。

//查询商品库存信息
select quantity,version from items where id=1;
//quantity = 3,version=1
//修改quantity为2
update items set quantity=2,version=2 where id=1 and version=1;
//如果quantity!=3可以认为有其它事务正在修改商品数量

更好的实现方法

只要保证库存>0,我们就可以认为操作成功。

update quantity=quantity-1 where quantity>0 and id=1;

这样减少了冲突和写入失败的次数。

悲观锁

悲观锁认为写数据的时候很容易发生冲突,因此会在开始执行事务的时候给表加锁。

情景

写多,预期可能会存在较多冲突

实现方法

使用数据库自带的锁机制

//0.开始事务
begin; 
//1.查询出商品库存信息
select quantity from items where id=1 for update;
//2.修改商品库存为2
update items set quantity=2 where id = 1;
//3.提交事务
commit;

for update可以将选择到的行预先加锁并等待,这个过程可能造成死锁。

MVCC多版本并发控制

这个东西是用来解决读写冲突的,关联隔离级别中的可重复读。

原理看起来也很简单,在写事务写入之前,建立一个只读快照。

当前读和快照读

当前读就是类似悲观锁的上锁机制,保证读取到最新版本并且读到的数据不会被其它事务修改。

快照读就是MVCC

Redis数据库

数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

事务

Redis中没有事务的概念,每个命令都是原子的,并且Redis是单线程的,所以不存在并发问题。但是,如果想要实现类似事务的操作,我们依旧可以使用multi和exec命令。

伪事务的实现

MULTI              # 开始事务
SET key1 value1    # 添加事务操作,设置键key1的值为value1
SET key2 value2    # 添加事务操作,设置键key2的值为value2
INCR key3          # 添加事务操作,将键key3的值自增1

EXEC               # 执行事务,将之前添加的所有操作一次性提交

但这并不完全算是事务,因为Redis中的操作虽然不会出现读写冲突,但是如果出现逻辑错误,比如说自增一个不存在的键这种操作,也会报错并拒绝执行,但是这个操作之前或之后的操作都会被执行。因此我们需要手动在程序中实现对报错的检查和数据回滚。Redis并没有帮我们做好这些实现。

常用于

Redis因为是内存数据库,具有读写操作快的优点,因此经常用来给SQL数据库作为缓存。当然,Redis也可以作为程序的唯一数据库。这就像飞机和火车一样,它和其它数据库并没有什么根本上的区别。