数据库(Database) 是保存有组织的数据的容器(通常是一个文件或一组文件),是通过 数据库管理系统(DataBaseManagement System,DBMS) 创建和操纵的容器。
DBMS 的主要目标是提供一种可以方便、高效地存取数据库信息的途径。
概念
数据库有两种类型,分别是 关系型数据库 和 非关系型数据库。
数据库类型 | 定义 | 优点 | 缺点 | 常见 |
---|---|---|---|---|
关系型数据库 | 建立在关系模型基础上,由多张能互相连接的 表 组成的数据库 |
|
|
MySQL,Microsoft SQL Server,Oracle,PostgreSQL 等 |
非关系型数据库 | 非关系型数据库又被称为 NoSQL(Not Only SQL ),通常指数据以 对象 的形式存储在数据库中,而对象之间的关系通过每个对象自身的属性来决定。 |
|
|
Neo4j,Redis,MongoDB 等 |
SQL 是 结构化查询语言(Structured Query Language) 的缩写,是一种数据库查询语言,用于存取数据、查询、更新和管理关系数据库系统。
MySQL 是一个 关系数据库管理系统(RDBMS),使用 SQL 执行所有数据库操作。
函数依赖
- 部分依赖:AB=>C,A=>C,B=>C,那么 C 部分依赖于 AB
- 完全依赖:AB=>C,A、B 不能单独=>C,那么 C 完全依赖于 AB
- 传递依赖:A=>B,B=>C,那么 C 传递依赖于 A
范式
数据库范式是设计数据库时,需要遵循的一些规范。各种范式是条件递增的联系,越高的范式数据库冗余越小。
常用的数据库三大范式为:
- 第一范式(1NF):每个列(属性)都不可以再拆分,强调的是列的原子性(不可分割)(属性不能有多个值或者不能有重复的属性)。简而言之,第一范式就是无重复的列。关系模式的基本要求
- 第二范式(2NF):在第一范式的基础上,一个表必须有一个主键,非主键列 完全依赖 于主键,而不能是依赖于主键的一部分。消除部分依赖
- 第三范式(3NF):在第二范式的基础上,非主键列只依赖(直接依赖,即不存在传递依赖)于主键,不依赖于其他非主键。消除传递依赖
索引
索引是一种 数据结构。数据库索引是 DBMS 中一个 排序的数据结构,以 协助快速查询、更新 数据库表中数据。
索引的实现通常使用 B 树以及变种 B+ 树。
以操作系统的观点,索引就相当于目录,是为了方便数据内容查找,本身也占用物理空间。
优点 | 缺点 |
|
|
索引分类
数据库索引根据结构分类,主要有 B 树索引(使用最频繁)、Hash 索引 和 位图索引 三种。
关于 B 树可以参考:B Tree
使用 B+ 树的优点:
- 由于 B+ 树的内部结点只存放键,不存放值,因此,一次读取,可以在同一内存页中获取更多的键,有利于更快地缩小查找范围。
- B+ 树的叶结点由一条链相连,因此当需要进行一次 全数据遍历 的时候,B+ 树只需要使用 O(logN) 时间找到最小结点,然后通过链进行 O(N) 的顺序遍历即可;或者,在找 大于某个关键字或者小于某个关键字的数据 的时候,B+ 树只需要找到该关键字然后沿着链表遍历即可。
Hash 索引和 B+ 树索引的区别:
- Hash 索引进行等值查询更快(一般情况下),但是却无法进行范围查询;
- Hash 索引不支持使用索引进行排序;
- Hash 索引不支持模糊查询以及多列索引的最左前缀匹配,原理也是因为 Hash 函数的不可预测;
- Hash 索引任何时候都避免不了回表查询数据,而 B+ 树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询;
- Hash 索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生 Hash 碰撞,此时效率可能极差;而 B+ 树的查询效率比较稳定,对于所有的查询都是从根结点到叶子结点,且树的高度较低。
聚簇索引
聚簇索引指将 数据存储 和 索引 放到一起,找到索引也就找到了数据。

事务
数据库的 事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。
如果任意一个操作失败,那么整组操作即为失败,会回到操作前状态或者是上一个节点。
因此,事务是保持 逻辑数据一致性 和 可恢复性 的重要利器。而锁是实现事务的关键,可以保证事务的完整性和并发性
特性
事务具有 4 个特性,通常简称为 ACID,关系型数据库 需要遵循 ACID 规则。
原子性(Atomicity)
事务是最小的执行单位,不可分割的(原子的)。事务的原子性确保动作要么全部执行,要么全部不执行。
以银行转账事务为例:
如果该事务提交了,则这两个账户的数据将会更新;
如果由于某种原因,事务在成功更新这两个账户之前终止了,则不会更新这两个账户的余额,并且会 撤销 对任何账户余额的修改,回到此操作前状态,即事务不能部分提交。
一致性(Consistency)
当事务完成时,数据必须处于一致状态,多个事务对同一个数据读取的结果是相同的。
以银行转账事务事务为例:
在事务开始之前,所有 账户余额的总额处于一致状态。
在事务进行的过程中,一个账户余额减少了,而另一个账户余额尚未修改。
因此,所有账户余额的总额处于不一致状态。
但是当事务完成以后,账户余额的总额再次恢复到一致状态。
隔离性(Isolation)
并发访问数据库 时,一个用户的事务不被其他事务所干扰,各个事务不干涉内部的数据。
修改数据的事务可以在另一个使用相同数据的事务开始之前访问这些数据,或者在另一个使用相同数据的事务结束之后访问这些数据。
持久性(Durability)
一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
实现
DBMS 采用 日志 来保证事务的 原子性、一致性 和 持久性。
日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。
DBMS 采用 锁机制 来实现事务的隔离性。
当多个事务同时更新数据库中相同的数据时,只允许 持有锁的事务 能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。
相应的原理
- 原子性:由 undo log 日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的 sql
- 一致性:一般由代码层面来保证
- 隔离性:由 MVCC 来保证
- 持久性:由内存 +redo log 来保证,mysql 修改数据同时在内存和 redo log 记录这次操作,事务提交的时候通过 redo log 刷盘,宕机的时候可以从 redo log 恢复
冲突
脏读(Dirty Read)
一个事务读取了另一个事务未提交的数据。
不可重复读(Non-repeatable Read)
就是在一个事务范围内,两次相同的查询会返回两个不同的数据,这是因为在此间隔内有其他事务对数据进行了修改。
幻读(Phantom Read)
幻读是指当事务 不是独立执行时 发生的一种现象,例如有一个事务对表中的数据进行了修改,这种修改涉及到表中的全部数据行,同时,第一个事务也修改这个表中的数据,这种修改是向表中 插入一行新数据。那么,第一个事务的用户发现表中还有没有修改的数据行,就好像发生了幻觉一样。
丢失更新(Lost Update)
两个事务同时读取同一条记录,事务 A 先修改记录,事务 B 也修改记录(B 是不知道 A 修改过),当 B 提交数据后, 其修改结果覆盖了 A 的修改结果,导致事务 A 更新丢失。
隔离级别
SQL 标准定义了 4 种不同的事务隔离级别(TRANSACTION ISOLATION LEVEL),即 并发事务对同一资源的读取深度层次。
由低到高依次是 读取未提交(READ-UNCOMMITTED)、读取已提交(READ-COMMITTED)、可重复读(REPEATABLE-READ)、可串行化(SERIALIZABLE)
隔离级别 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
未提交 (RU) | 只能保证不读取物理上的损坏 | √ | √ | √ |
已提交 (RC) | 语句级 | × | √ | √ |
可重复读 (RR) | 事务级 | × | × | √ |
串行化 (SR) | 最高级别,事务级 | × | × | × |
锁
从数据库系统的角度,锁模式可分为以下 6 种类型:
共享锁(S):又叫 他读锁。
可以并发读取数据,但不能修改数据。也就是说当数据资源上存在共享锁时,所有的事务都不能对该数据进行修改,直到数据读取完成,共享锁释放。
排它锁(X):又叫 独占锁、写锁。
对数据资源进行增删改操作时,不允许其它事务操作这块资源,直到排它锁被释放,从而防止同时对同一资源进行多重操作。
更新锁(U):
防止出现 死锁 的锁模式,两个事务对一个数据资源进行先读取再修改的情况下,使用共享锁和排它锁有时会出现死锁现象,而使用更新锁就可以避免死锁的出现。
资源的更新锁一次只能分配给一个事务,如果需要对资源进行修改,更新锁会变成排它锁,否则变为共享锁。
意向锁:
表示 SQL Server 需要在 层次结构中的某些底层资源上 获取共享锁或排它锁。
例如,放置在 表级 的 共享意向锁 表示事务打算在表中的页或行上放置共享锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排它锁。
意向锁可以提高性能,因为 SQL Server 仅在 表级 检查意向锁来确定事务是否可以安全地获取该表上的锁,而无须检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。
意向锁包括意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。
架构锁:
在执行 依赖于表架构的操作 时使用。架构锁的类型为:架构修改 (Sch-M) 和架构稳定性 (Sch-S),执行表的数据定义语言 (DDL)操作(例如添加列或除去表)时使用架构修改锁,当编译查询时,使用架构稳定性锁。
大容量更新锁(BU):
向表中大容量复制数据并指定了
TABLOCK
提示时使用。 大容量更新锁允许进程将数据并发地大容量复制到同一表,同时防止其它不进行大容量复制数据的进程访问该表。
关系
- 在 读取未提交 隔离级别下,读取数据不需要加 共享锁,这样就不会跟被修改的数据上的 排他锁 冲突;
- 在 读取已提交 隔离级别下,读操作需要加 共享锁,但是在语句执行完以后释放共享锁;
- 在 可重复读 隔离级别下,读操作需要加 共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁;
- 可串行化 是限制性最强的隔离级别,因为该级别 锁定整个范围的键,并一直持有锁,直到事务完成。
死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法
- 如果不同程序并发存取多个表,尽量约定 以相同的顺序访问表,可以大大降低死锁机会;
- 在同一个事务中,尽可能做到 一次锁定所需要的所有资源,减少死锁产生概率;
- 对于非常容易产生死锁的业务部分,可以尝试使用 升级锁定颗粒度,通过 表级锁 定来减少死锁产生的概率。
乐/悲观锁
DBMS 中的 并发控制 的任务是确保在 多个事务同时存取数据库中同一数据 时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。这对于长事务来讲,可能会严重影响系统的并发处理能力。实现方式:使用数据库中的锁机制。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁适用于 读多写少 的应用场景,这样可以提高吞吐量。实现方式:一般会使用版本号机制或 CAS 算法实现。