个人成长博客

纸上得来终觉浅,绝知此事要躬行

0%

数据库基础

概述

数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条数据。但是数据库并不是随意地将数据进行存放,是有一定的规则的,否则查询的效率会很低。这里主要介绍关系型数据库。关系型数据库,存储的格式可以直观地反映实体间的关系。关系型数据库和常见的表格比较相似,关系型数据库中表与表之间是有很多复杂的关联关系的。 常见的关系型数据库有Mysql,SqlServer等。在轻量或者小型的应用中,使用不同的关系型数据库对系统的性能影响不大,但是在构建大型应用时,则需要根据应用的业务需求和性能需求,选择合适的关系型数据库。数据库的设计主要分为存储系统和程序实例两部分,程序实例又分为存储管理、缓存实例、SQL解析、日志管理、权限划分、容灾机制、索引管理、锁管理等。

索引模块

为什么要使用索引

全表扫描,当只有小部分数据,全表扫描可能比根据索引查找还快。数据量比较大的时候,因为存储的最小单位为块或者页,会把所有块或者页遍历查找,然后找到我们所需要的数据并返回,这时候全表扫描就显得不是很合适。索引和字典有异曲同工之处,将一些关键信息组织起来,比如偏旁部首,查询的时候依据这些信息,就能够快速找到想要的数据。所以使用索引能够快速查找数据。

什么样的信息能成为索引

将数据能够限定在一定查找范围之内的字段可以成为索引,主键、唯一键以及普通键等都可以成为索引。

索引的数据结构

二叉树索引

生成索引,建立二叉查找树进行二分查找:

其中P*为索引,使用二叉树作为索引,因为是二分查找,所以查询的时间复杂度为O(log n),但当二叉树变成线性二叉树的时候,查询的时间复杂度则降为O(n)。可以通过树的旋转解决查询效率退化的问题,但是因为每次进行深层的二分查询都会有IO,影响数据库的性能主要也是IO,所以减少不必要的IO才能进一步扩大性能。

B- 树索引

B-tree,即B树,而不要读成B减树,它是一种多路搜索树(并不是二叉的),运用B-tree可以让每个索引块尽可能存储更多的信息(不想二叉树每个节点只能存储二个关键信息):

1.定义任意非叶子结点最多只有M个儿子;且M>2;

2.根结点的儿子数为[2, M];

3.**除根结点以外的非叶子结点的儿子数为[M/2, M]**;

4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)

5.非叶子结点的关键字个数=指向儿子的指针个数-1;

6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

8.所有叶子结点位于同一层;

如:(M=3)

B+ 树索引

B+树是B树的变体,其定义基本与B树相同,一颗m阶的B+树和m阶的B树的差异在于:

  1. 有n棵子树的结点中含有n个关键字; (而B树是n棵子树有n-1个关键字)
  2. 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。(而B树的叶子节点并没有包括全部需要查找的信息)
  3. 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)

B+-树更适合作为存储索引:

  1. B+-tree的磁盘读写代价更低

    B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

    举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。

  2. B+-tree的查询效率更加稳定

    由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

  3. B+-tree更有利于对数据库的扫描

Hash索引

Hash索引根据key的hash值来创建哈希表存储数据,当知道具体key时能够快速查找到该条记录。

Hash索引也有些缺点:

  1. 仅仅能满足“=”、“IN”,不能使用范围查询
  2. 无法被用来避免数据的排序操作
  3. 不能利用部分索引键查询
  4. 不能避免表扫描
  5. 遇到大量Hash值相等的情况后性能不一定就会比B-Tree索引高

BitMap索引

位图索引只适合某个字段的值是固定的几个,位图索引的锁的粒度比较大。

密集索引和稀疏索引的区别

主要区别:

  1. 密集索引文件中的每个搜索码值都对应一个索引值
  2. 稀疏索引文件只为索引码的某些值建立索引项

InnoDB

  1. 若一个主键被定义,该主键则作为密集索引
  2. 若没有主键被定义,该表的第一个唯一非空索引则作为密集索引
  3. 若不满足以上条件,innodb内部会生成一个隐藏主键(密集索引)
  4. 非主键索引存储相关键位和其对应的主键值,包含两次查找

联合索引

最左匹配原则:

  1. 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a=1 and b=4 and c>5 and d=6如果建立(a,b,c,d)顺序的索引,d是用不到索引的;如果建立的是(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整
  2. =和in可以乱序,比如a=1 and b=2 and c=3建立(a,b,c) 索引可以任意顺序,mysql的查询优化器会帮助你优化成索引可以识别的形式

最左匹配原则的成因:

leftrule

如图是根据(col3,col2)组成的联合索引,建立联合索引时会根据第一个索引进行排序,然后在此基础上再根据第二个索引进行排序,以此类推。那么第一个字段是绝对有序,直接使用第二个字段则无须。

聚集索引和非聚集索引

  1. 聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个
  2. 聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续
  3. 聚集索引:物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序
  4. 非聚集索引:物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序
  5. 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块

索引是建立的越多越好呢

  1. 数据量小的表不需要建立索引,建立会增加额外的索引开销
  2. 数据变更需要维护索引,因此更多的索引意味更多的维护成本
  3. 更多的索引意味着也需要更多的空间

锁模块

数据库锁的分类

  1. 按锁的粒度划分,可分为表级锁、行级锁、页级锁
  2. 按锁级别划分,可分为共享锁、排它锁
  3. 按加锁方式划分,可分为自动锁、显示锁
  4. 按操作划分,可分为DML锁、DDL锁
  5. 按使用方式划分,可分为乐观锁、悲观锁

MyISAM与InnoDB关于锁方面的区别

主要区别:

  1. MyISAM默认用的是表级锁,不支持行级锁;适用场景,频繁执行全表count语句、对数据增删改的频率不高,查询非常频繁、没有事务
  2. InnoDB默认用的是行级锁,也支持表级锁;数据增删改查都相当频繁、可靠性要求比较高,要求支持事务

数据库事务四大特性

ACID

  1. 原子性(Atomic):事务包含的操作要么全做,要么全不做
  2. 一致性(Consistency):事务应确保数据库的状态从一个一个一致性转变为另一个一致性
  3. 隔离性(Isolation):多个事务并发执行时,一个事务的执行,不应该影响另一个事务的执行
  4. 持久性(Durability):一个事务一旦提交,则应该永久保留在数据库中,故障前提交的事务,应该确保被执行

事务隔离级别以及各级别下的并发访问问题

  1. 更新丢失:mysql所有事务隔离级别在数据库层面上均可避免

    取款事务 存款事务
    开始事务 开始事务
    查询账户余额为100元
    查询转账余额为100元
    存入20元,余额变成120元
    提交事务
    取出10元,余额改为90元
    回滚事务,余额恢复为100元 更新丢失
  2. 脏读:脏读是读到了别的事务回滚前的脏数据,READ-COMMITTED事务隔离级别以上可避免

    取款事务 存款事务
    开始事务 开始事务
    原来余额100,取出10元,变为90,未提交事务
    查询余额为90
    回滚事务
    存20元,提交事务。
    余额变成110(脏读)
  3. 不可重复读:是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。REPEATABLE-READ事务隔离级别以上可避免

    取款事务 查询
    开始事务 开始事务
    查询余额100元
    原来余额100,取出10元,变为90,提交事务
    查询余额90元(同一事务两次读取不一致)
  4. 幻读:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行。SERIALIZABLE事务隔离级别可避免

    查询事务 插入事务
    开始事务 开始事务
    查询所有数据,假设一共有三条
    插入一条数据
    更新所有数据的某一字段,update时影响了四条数据(幻读)
事务隔离级别 更新丢失 脏读 不可重复读 幻读
未提交读 避免 发生 发生 发生
已提交读 避免 避免 发生 发生
可重复读 避免 避免 避免 发生
串行化 避免 避免 避免 避免

InnoDB可重复读隔离级别下如何避免幻读

首先先介绍下什么是当前读和快照读:

  1. 当前读:加锁的增删改查语句,无论什么锁,因为读取的是当前最新版本,还要保证并发事务不能修改当前记录,对读取记录加锁。例如:select … lock in share mode、select … for update会加共享锁,update、delete、insert会加排它锁
  2. 快照读:可能读取到的数据不是最新版本而是历史版本

注意:已提交读(read committed)级别下。当前读与快照读读取的版本一样。可重复读(repeatable read) 级别下,当前读返回的是数据的最新版本,**快照读返回的可能数据未修改前的版本也可能是最新的数据版本。因为在RR级别下,事务调用快照读的时机很重要,创建快照的时机决定了读取的版本。

RC、RR级别下的InnoDB的快照读(非阻塞读)如何实现

数据行关键字段
  1. DB_TRX_ID:该字段标明最近一次对数据做修改事务的标识符
  2. DB_ROLL_PTR:回滚指针,写入回滚段的undo日志
  3. DB__ROW_ID :行号,随着新行出现单调递增的id
undo日志

主要分为insert undo日志(事务回滚涉及)和update undo日志(事务回滚和快照读都涉及)。对事务变更就会产生undo记录,存储的是老版数据,事务回滚需要。
undo日志工作方式,举例来说,如下图:

当事务1,对于DB_ROW_ID某一个字段进行修改时,首先用排它锁锁定改行数据,然后会copy一份修改前的数据到undo log,然后修改当前行的数据,更新事务id字段,使用回滚指定指向undo log中修改前的行,以此类推。

ReadView

主要取出DB_TRX_ID和系统其它活跃的事务id相对比,大于或者等于事务id时,然后根据DB_ROLL_PTR指针从undo log中取出当前版本

MVCC

MVCC( Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程。可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。 READ COMMITTD、 REPEATABLE READ这两个隔离级别的一个很大不同就是:生成ReadView的时机不同, READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。

InnoDB可重复读隔离级别下如何避免幻读

  • 表象:快照读(非阻塞读)—伪MVCC
  • 内在:next-key锁,(行锁+gap锁)

next-key锁,由行锁和gap锁组成。行锁就是对行记录加的锁。通过Next-key锁,InnoDB读取数据时,会增加锁,以保证事务内读取数据的一致性

Gap锁

Gap是索引树种插入新纪录的空隙,Gap锁定一个范围但不包括本身。

  1. 目的是防止同一事务的两次当前读出现幻读的情况

  2. Repeatable-read级别以上支持Gap锁

  3. 在RR级别下,对主键索引或者唯一索引会用Gap锁的情况:

    • 如果where条件全部命中,则不会用Gap锁,只会加行锁,Select * from . where id=1,2,3 这三条记录都能查得到,则称为全部命中
    • 如果where条件部分命中或者全不命中,则会加Gap锁
  4. Gap锁会用在非唯一索引或者不走索引的当前读中

    (非唯一索引为啥需要gap锁,因为不是唯一,所以可能存在多条一样有不确定性)

    • 非唯一索引Gap锁,如下图,这里操作删除id为9的数时,(6,9]和(9,11]会上Gap锁

    • 不走索引的Gap锁,如下图,会对所有记录都上锁,相当于锁表