InnoDB 是 MySQL 5.5 以后默认的存储引擎,它具备高可靠、高性能的特色,主要具有如下优点:html
一个 InnoDB 引擎完整的内存结构和磁盘结构以下图所示:mysql
MyISAM 是 MySQL 5.5 以前默认的存储引擎。建立 MyISAM 表时会建立两个同名的文件:git
.MYD
(MYData
):用于存储表数据;.MYI
(MYIndex
): 用于存储表的索引信息。在 MySQL 8.0 以后,只会建立上述两个同名文件,由于 8.0 后表结构的定义存储在 MySQL 数据字典中,但在 MySQL 8.0 以前,还会存在一个扩展名为 .frm
的文件,用于存储表结构信息。MyISAM 与 InnoDB 主要的区别其只支持表级锁,不支持行级锁,不支持事务,不支持自动崩溃恢复,但可使用内置的 mysqlcheck 和 myisamchk 工具来进行检查和修复。github
MEMORY 存储引擎(又称为 HEAP 存储引擎)一般用于将表中的数据存储在内存中,它具备如下特征:算法
基于以上特性,MEMORY 表主要适合于存储临时数据 ,如会话状态、实时位置等信息。sql
CSV 存储引擎使用逗号分隔值的格式将数据存储在文本文件中。建立 CSV 表时会同时建立两个同名的文件:数据库
csv
,负责存储表的数据,其文件格式为纯文本,能够经过电子表格应用程序 (如 Microsoft Excel ) 进行修改,对应的修改操做也会直接反应在数据库表中。CSM
,负责存储表的状态和表中存在的行数。ARCHIVE 存储引擎默认采用 zlib 无损数据压缩算法进行数据压缩,可以利用极小的空间存储大量的数据。建立ARCHIVE 表时,存储引擎会建立与表同名的 ARZ
文件,用于存储数据。它还具备如下特色:缓存
MERGE 存储引擎,也称为 MRG_MyISAM 引擎,是一组相同 MyISAM 表的集合。 ”相同” 表示全部表必须具备相同的列数据类型和索引信息。能够经过 UNION = (list-of-tables)
选项来建立 MERGE 表,以下:安全
mysql> CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;
mysql> CREATE TABLE t2 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;
mysql> INSERT INTO t1 (message) VALUES ('Testing'),('table'),('t1');
mysql> INSERT INTO t2 (message) VALUES ('Testing'),('table'),('t2');
mysql> CREATE TABLE total (a INT NOT NULL AUTO_INCREMENT,message CHAR(20), INDEX(a))
ENGINE=MERGE UNION=(t1,t2) INSERT_METHOD=LAST;
复制代码
建立表时能够经过 INSERT_METHOD
选项来控制 MERGE 表的插入:使用 FIRST
或 LAST
分别表示在第一个或最后一个基础表中进行插入;若是未指定 INSERT_METHOD 或者设置值为 NO ,则表示不容许在 MERGE 表上执行插入操做。MERGE 表支持 SELECT,DELETE,UPDATE 和 DELETE 语句,示例以下:服务器
mysql> SELECT * FROM total;
+---+---------+
| a | message |
+---+---------+
| 1 | Testing |
| 2 | table |
| 3 | t1 |
| 1 | Testing |
| 2 | table |
| 3 | t2 |
+---+---------+
复制代码
若是没有特殊说明,一般大多数数据库采用的索引都是 B+ tree 索引,它是基于 B+ tree 这种数据结构构建的。为何采用 B+ tree 而不是平衡二叉树 (AVL) 或红黑树等数据结构?这里假设索引为 1-16 的自增数据,各种数据结构的表现以下:
平衡二叉树数据结构:
红黑树数据结构:
Btree 数据结构:
B+ Tree 数据结构
以上图片均经过数据结构可视化网站 Data Structure Visualizations 自动生成,感兴趣的小伙伴也可自行尝试。
从上面的图示中咱们能够看出 B+ Tree 树具备如下优势:
对于 InnoDB ,由于主键索引是汇集索引,因此其叶子节点存储的就是实际的数据。而非主键索引存储的则是主键的值 :
对于 MyISAM,由于主键索引是非汇集索引,因此其叶子节点存储的只是指向数据位置的指针:
综上所述,B+ tree 结构广泛适用于范围查找,优化排序和分组等操做。B+ tree 是基于字典序进行构建的,所以其适用于如下查询:
emp_no
字段为索引,查询条件为 emp_no = 10008
。emp_no
和 dept_no
为联合索引,查找条件为 emp_no = 10008
。dept_no
为索引,查询条件为 dept_no like "d1%"
。前缀匹配和列前缀匹配都是索引前缀性的体现,在某些时候也称为前缀索引。emp_no
字段为索引,查询条件为 emp_no > 10008
。emp_no
字段为索引,查询语句为 select emp_no from employees
,此时 emp_no 索引被称为本次查询的覆盖索引,即只须要从索引上就能够获取所有的查询信息,而没必要访问实际的表中的数据。emp_no
和 dept_no
为联合索引,查找条件为 dept_no = "d004" and emp_no < 10020
,这种状况下索引顺序必须为 ( emp_no,dept_no ),这样才能基于 emp_no 的字典序排序进行范围查找。使用哈希索引时,存储引擎会对索引列的值进行哈希运算,并将计算出的哈希值和指向该行数据的指针存储在索引中,所以它更适用于等值比较查询,而不是范围查询,一样也不能用于优化排序和分组等操做。在创建哈希索引时,须要选取选择性比较高的列,即列上的数据不容易重复 (如身份证号),这样能够尽可能避免哈希冲突。由于哈希索引并不须要存储索引列的数据,因此其结构比较紧凑,对应的查询速度也比较快。
InnoDB 引擎有一个名为 “自适应哈希索引 (adaptive hash index)” 的功能,当某些索引值被频繁使用时,它会在内存中基于 B+ tree 索引再建立一个哈希索引,从而让 B-Tree 索引具有哈希索引快速查找的优势。
InnoDB 存储引擎支持如下两种标准的行级锁:
排它锁和共享锁的兼容状况以下:
X | X | |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
为了说明意向锁的做用,这里先引入一个案例:假设事务 A 利用 S 锁锁住了表中的某一行,让其只能读不能写。以后事务 B 尝试申请整个表的写锁,若是事务 B 申请成功,那么理论上它就应该能修改表中的任意一行,这与事务 A 持有的行锁是冲突的。想要解决这个问题,数据库必须知道表中某一行已经被锁定,从而在事务 B 尝试申请整个表的写锁时阻塞它。想要知道表中某一行被锁定,能够对表的每一行进行遍历,这种方式可行可是性能比较差,因此 InnoDB 引入了意向锁。
按照意向锁的规则,当上面的事务 A 给表中的某一行加 S 锁时,会同时给表加上 IS 锁,以后事务 B 尝试获取表的 X 锁时,因为 X 锁与 IS 锁并不兼容,因此事务 B 会被阻塞。
X | IX | S | IS | |
---|---|---|---|---|
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
IX | 不兼容 | 兼容 | 不兼容 | 兼容 |
S | 不兼容 | 不兼容 | 兼容 | 兼容 |
IS | 不兼容 | 兼容 | 兼容 | 兼容 |
1. 一致性非锁定读
一致非锁定读 (consistent nonlocking read) 是指在 InnoDB 存储引擎下,若是将要读取的行正在执行 DELETE 或 UPDATE 操做,此时没必要去等待行上锁的释放,而是去读取 undo 日志上该行的快照数据,具体以下:
基于多版本并发控制和一致性非锁定读,能够避免获取锁的等待,从而提升并发访问下的性能。
2. 一致性锁定度
一致性锁定读则容许用户按照本身的需求在进行 SELECT 操做时手动加锁,一般有如下两种方式:
InnoDB 存储引擎支持如下三种锁的算法:
Record Lock:行锁,用于锁定单个行记录。示例以下:
-- 利用行锁能够防止其余事务更新或删除该行
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
复制代码
Gap Lock:间隙锁,锁定一个范围,但不包括记录自己,主要用于解决幻读问题,示例以下:
-- 利用间隙锁能够阻止其余事务将值15插入列 t.c1
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
复制代码
Next-Key Lock:等价于 行锁+间隙锁,既锁定范围,也锁定记录自己。能够用于解决幻读中的 ”当前读“ 的问题。
InnoDB 存储引擎彻底支持 ACID 模型:
1. 原子性(Atomicity)
事务是不可分割的最小工做单元,事务的全部操做要么所有提交成功,要么所有失败回滚,不存在部分红功的状况。
2. 一致性(Consistency)
数据库在事务执行先后都保持一致性状态,数据库的完整性没有被破坏。
3. 隔离性(Isolation)
容许多个并发事务同时对数据进行操做,但一个事务所作的修改在最终提交之前,对其它事务是不可见的。
4. 持久性(Durability)
一旦事务提交,则其所作的修改将会永远保存到数据库中。即便宕机等故障,也不会丢失。
数据库隔离性由上一部分介绍的锁来实现,而原子性、一致性、持久性都由 undo log 和 redo log 来实现。
在并发环境下,数据的更改一般会产生下面四种问题:
1.丢失更新
一个事务的更新操做被另一个事务的更新操做锁覆盖,从而致使数据不一致:
2. 脏读
在不一样的事务下,一个事务读取到其余事务未提交的数据:
3. 不可重复读
在同一个事务的两次读取之间,因为其余事务对数据进行了修改,致使对同一条数据两次读到的结果不一致:
4.幻读
在同一个事务的两次读取之间,因为其余事务对数据进行了修改,致使第二次读取到第一次不存在数据,或第一次本来存在的数据,第二次却读取不到,就好像以前的读取是 “幻觉” 同样:
想要解决以上问题,能够经过设置隔离级别来实现:InnoDB 支持如下四个等级的隔离级别,默认隔离级别为可重复读:
在每一个级别下,并发问题是否可能出现的状况以下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | 可能出现 | 可能 | 可能 |
读已提交(READ COMMITTED) | 不可能出现 | 可能 | 可能 |
可重复读(REPEATABLE READ) | 不可能 | 不可能 | 可能 |
串行化(SERIALIZABLE) | 不可能 | 不可能 | 不可能 |
就数据库层面而言,当前任何隔离级别下都不会发生丢失更新的问题,以 InnoDB 存储引擎为例,若是你想要更改表中某行数据,该行数据上必然会加上 X 锁,而对应的表上则会加上 IX 锁,其余任何事务必须等待获取该锁才能进行修改操做。
数据库设计当中经常使用的三范式以下:
要求表中的每一列都是不可再细分的原子项。这是最低的范式要求,一般都可以被知足。
要求非主键列必须彻底依赖于主键列,而不能存在部分依赖。示例以下:
mechanism_id (组织机构代码) | employee_id (雇员编号) | ename (雇员名称) | mname (机构名称) |
---|---|---|---|
28193182 | 10001 | heibaiying | XXXX公司 |
以上是一张全市在职人员统计表,主键为:机构编码 + 雇员编号。表中的雇员名称彻底依赖于此联合主键,但机构名称却只依赖于机构编码,这就是部分依赖,所以违背了第二范式。此时经常使用的解决方式是创建一张组织机构与组织名称的字典表。
非主键列不能依赖于其余非主键列,若是其余非主键列又依赖于主键列,此时就出现了传递依赖。示例以下:
employee_id (雇员编号) | ename (雇员名称) | dept_no (部门编号) | dname(部门名称) |
---|---|---|---|
10001 | heibaiying | 06 | 开发部 |
以上是一张雇员表,雇员名称和所属的部门编号都依赖于主键 employee_id ,但部门名称却依赖于部门编号,此时就出现了非主键列依赖于其余非主键列,这就违背的第三范式。此时经常使用的解决方式是创建一张部门表用于维护部门相关的信息。
从上面的例子中咱们也能够看出,想要彻底遵循三范式设计,可能须要额外增长不少表来进行维护。因此在平常开发中,基于其余因素的综合考量,可能并不会彻底遵循范式设计,甚至可能违反范式设计,这就是反范式设计。
更多文章,欢迎访问个人 GitHub 仓库:Full-Stack-Notes !