第一范式:列不可分,
eg:【联系人】(姓名,性别,电话),一个联系人有家庭电话和公司电话,那么这种表结构设计就没有达到 1NF;
第二范式:有主键,保证完全依赖。
eg:订单明细表【OrderDetail】(OrderID,ProductID,UnitPrice,Discount,Quantity,ProductName),Discount,Quantity完全依赖(取决)于主键(OderID,ProductID),而 UnitPrice,ProductName 只依赖于 ProductID,不符合2NF;
第三范式:无传递依赖(非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况),
eg:订单表【Order】(OrderID,OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity)主键是(OrderID),CustomerName,CustomerAddr,CustomerCity 直接依赖的是 CustomerID(非主键列),而不是直接依赖于主键,它是通过传递才依赖于主键,所以不符合 3NF。
(美团面试时让举出三个范式的例子)
索引是对数据库表中一个或多个列的值进行排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B_TREE及其变种。索引加速了数据访问,因为存储引擎不会再去扫描整张表得到需要的数据;相反,它从根节点开始,根节点保存了子节点的指针,存储引擎会根据指针快速寻找数据。
上图显示了一种索引方式。左边是数据库中的数据表,有col1和col2两个字段,一共有15条记录;右边是以col2列为索引列的B_TREE索引,每个节点包含索引的键值和对应数据表地址的指针,这样就可以都过B_TREE在 O(logn) 的时间复杂度内获取相应的数据,这样明显地加快了检索的速度。
(1) B-Tree(平衡多路查找树)
B_TREE是一种平衡多路查找树,是一种动态查找效率很高的树形结构。一颗m阶的B_TREE或是一颗空树,或者是满足下列条件的m叉树:
树中每个结点最多有m个孩子结点;
若根结点不是叶子节点,则根结点至少有2个孩子结点;
除根结点外,其它结点至少有(m/2的上界)个孩子结点;
结点的结构如下图所示,其中,n为结点中关键字个数,(m/2的上界)-1 <= n <= m-1;di(1<=i<=n)为该结点的n个关键字值的第i个,且di< d(i+1);ci(0<=i<=n)为该结点孩子结点的指针,且ci所指向的节点的关键字均大于或等于di且小于d(i+1);
所有的叶结点都在同一层上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)。
下图是一棵4阶B_TREE,4叉树结点的孩子结点的个数范围[2,4]。
从上图能轻易的看到,一个内结点x若含有n[x]个关键字,那么x将含有n[x]+1个子女。如含有2个关键字D H的内结点有3个子女,而含有3个关键字Q T X的内结点有4个子女。
B_TREE可以用阶定义,也可以用度定义。
B_TREE的查找类似二叉排序树的查找,所不同的是B-树每个结点上是多关键码的有序表,在到达某个结点时,先在有序表中查找,若找到,则查找成功;否则,到按照对应的指针信息指向的子树中去查找,当到达叶子结点时,则说明树中没有对应的关键码。由于B_TREE的高检索效率,B-树主要应用在文件系统和数据库中,对于存储在硬盘上的大型数据库文件,可以极大程度减少访问硬盘次数,大幅度提高数据检索效率。
假如每个盘块可以正好存放一个B树的结点(正好存放2个文件名)。那么一个BTNODE结点就代表一个盘块,而子树指针就是存放另外一个盘块的地址。模拟下查找文件29的过程:
<1> 根据根结点指针找到文件目录的根磁盘块1,将其中的信息导入内存。【磁盘IO操作 1次】。
此时内存中有两个文件名17、35和三个存储其他磁盘页面地址的数据。根据算法我们发现:17<29<35,因此我们找到指针p2。
<2> 根据p2指针,我们定位到磁盘块3,并将其中的信息导入内存。【磁盘IO操作 2次】。
此时内存中有两个文件名26,30和三个存储其他磁盘页面地址的数据。根据算法我们发现:26<29<30,因此我们找到指针p2。
<3> 根据p2指针,我们定位到磁盘块8,并将其中的信息导入内存。【磁盘IO操作 3次】。
此时内存中有两个文件名28,29。根据算法我们查找到文件名29,并定位了该文件内存的磁盘地址。
分析上面的过程,发现需要3次磁盘IO操作和3次内存查找操作。关于内存中的文件名查找,由于是一个有序表结构,可以利用折半查找提高效率。至于IO操作是影响整个B树查找效率的决定因素。
当然,如果我们使用平衡二叉树的磁盘存储结构来进行查找,磁盘4次,最多5次,而且文件越多,B树比平衡二叉树所用的磁盘IO操作次数将越少,效率也越高。
一棵含有N个总关键字数的m阶的B树的最大高度是多少?
B_TREE高度:h= log┌m/2┐((N+1)/2 )+1。
(2). B+Tree : InnoDB存储引擎的索引实现
B+Tree是应文件系统所需而产生的一种B_TREE树的变形树。一棵m阶的B+树和m阶的B_TREE的差异在于以下三点:
<1> n 棵子树的结点中含有n个关键码;
<2>所有的叶子结点中包含了全部关键码的信息,及指向含有这些关键码记录的指针,且叶子结点本身依关键码的大小自小而大的顺序链接;(而B树的叶子节点并没有包括全部需要查找的信息)
<3>非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键码。 (而B 树的非终节点也包含需要查找的有效信息)
下图为一棵3阶的B+树。通常在B+树上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点。因此可以对B+树进行两种查找运算:一种是从最小关键字起顺序查找,另一种是从根节点开始,进行随机查找。 在B+树上进行随机查找、插入和删除的过程基本上与B-树类似。只是在查找时,若非终端结点上的关键码等于给定值,并不终止,而是继续向下直到叶子结点。因此,对于B+树,不管查找成功与否,每次查找都是走了一条从根到叶子结点的路径。
B+tree的磁盘读写代价更低:B+tree的内部结点并没有指向关键字具体信息的指针(红色部分),因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多,相对来说IO读写次数也就降低了;
文件与数据库都是需要较大的存储,也就是说,它们都不可能全部存储在内存中,故需要存储到磁盘上。而所谓索引,则为了数据的快速定位与查找,那么索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,因此B+树相比B树更为合适。
B+tree的查询效率更加稳定:任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当;
数据库索引采用B+树而不是B树的主要原因:B+树只要遍历叶子节点就可以实现整棵树的遍历,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低。
数据库系统巧妙利用了局部性原理与磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入,而红黑树这种结构,高度明显要深的多,并且由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性。
https://www.cnblogs.com/guojin705/archive/2011/06/21/2086429.html
优点:
(1)大大加快数据的检索速度,这也是创建索引的最主要的原因;
(2)加速表和表之间的连接;
(3)在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间;
(4)通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性;
缺点:
(1)时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度;
(2)空间方面:索引需要占物理空间。
https://blog.csdn.net/u012758088/article/details/77113122
(1)主键、自增主键、主键索引与唯一索引概念区别
主键:指字段 唯一、不为空值 的列;
主键索引:指的就是主键,主键是索引的一种,是唯一索引的特殊类型。创建主键的时候,数据库默认会为主键创建一个唯一索引;
自增主键:字段类型为数字、自增、并且是主键;
唯一索引:索引列的值必须唯一,但允许有空值。主键是唯一索引,这样说没错;但反过来说,唯一索引也是主键就错误了,因为唯一索引允许空值,主键不允许有空值,所以不能说唯一索引也是主键。
(2)索引分类
普通索引和唯一索引:索引列的值的唯一性
单个索引和复合索引:索引列所包含的列数
聚集索引与非聚集索引:聚集索引按照数据的物理存储进行划分的。该索引中键值的逻辑顺序决定了表中相应行的物理顺序。对于一堆记录来说,使用聚集索引就是对这堆记录进行堆划分,主要描述的是物理上的存储。这种划分方法,导致聚集索引必须是唯一的。聚集索引可以帮助把很大的范围,迅速减小范围。但是查找该记录,就要从这个小范围中Scan了;而非聚集索引中的项按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。对于非聚集索引,可以为在表非聚集索引中查找数据时常用的每个列创建一个非聚集索引。
Eg:字典按拼音查:聚集索引; 按偏旁查:非聚集索引
(3)主键就是聚集索引吗?主键和索引有什么区别?
主键是一种特殊的唯一性索引,其可以是聚集索引,也可以是非聚集索引。在SQLServer中,主键的创建必须依赖于索引,默认创建的是聚集索引,但也可以显式指定为非聚集索引。InnoDB作为MySQL存储引擎时,默认按照主键进行聚集,如果没有定义主键,InnoDB会试着使用唯一的非空索引来代替。如果没有这种索引,InnoDB就会定义隐藏的主键然后在上面进行聚集。所以,对于聚集索引来说,你创建主键的时候,自动就创建了主键的聚集索引。
(1)应尽量避免在 where 子句中使用 != 或 <> 操作符、函数操作、表达式操作,否则引擎将放弃使用索引而进行全表扫描;
(2)尽量避免在 where 子句中使用 or 来连接条件,否则引擎放弃使用索引而进行全表扫描,即使其中有条件带索引也不会使用;
(3)对于多列索引,不是使用的第一部分,则不会使用索引;(最左匹配原则) (eg:多列索引col1、col2和col3,则 索引生效的情形包括 col1或col1,col2或col1,col2,col3)
(4)如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不会使用索引;
(5)like的模糊查询以 % 开头,索引失效
经常作查询选择的字段
经常作表连接的字段
经常出现在order by, group by, distinct 后面的字段
非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。应该用0、一个特殊的值或者一个空串代替空值;
取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多,字段的离散程度越高;
索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多,一次IO操作获取的数据越大,效率越高。
https://my.oschina.net/u/192807/blog/49091
每种数据库的数据格式,内部实现机制都是不同的,要利用一种开发工具访问一种数据库,就必须通过一种中介程序,这种开发工具与数据库之间的中介程序就叫数据库引擎。
引擎(engine)能够决定程序管理和数据操作的程序或程序段。数据库引擎就是操作数据库的一段程序或程序段。
MySQL有以下几种引擎:ISAM、MyISAM、HEAP、InnoDB和Berkley(BDB)
(1)ISAM
ISAM 是一个数据表格管理方法,它在设计之时就考虑到数据库被查询的次数要远大于更新的次数。
优点:读取操作的速度快,而且不占用大量的内存和存储资源。
缺点:不支持事务处理,也不能够容错:如果你的硬盘崩溃了,那么数据文件就无法恢复了。必须经常备份你所有的实时数据。
(2)MyISAM
MyISAM 是MySQL的 ISAM扩展格式和缺省的数据库引擎。
优点:提供ISAM里所没有的索引和字段管理的大量功能;MyISAM还使用一种表格锁定的机制,来优化多个并发的读写操作。
缺点:其代价是你需要经常运行OPTIMIZE TABLE命令,来恢复被更新机制所浪费的空间。MyISAM还有一些有用的扩展,例如用来修复数据库文件的MyISAMChk工具和用来恢复浪费空间的 MyISAMPack工具。
MyISAM强调了快速读取操作,在 Web开发中大量数据操作都是读取操作。所以,大多数虚拟主机提供商和Internet平台提供商(Internet Presence Provider,IPP)只允许使用MyISAM格式。
(3)HEAP
HEAP允许只驻留在内存里的临时表格。驻留在内存里让HEAP要比ISAM和MyISAM 都快,但是它所管理的数据是不稳定的,而且如果在关机之前没有进行保存,那么所有的数据都会丢失。在数据行被删除的时候,HEAP也不会浪费大量的空间。HEAP表格在你需要使用SELECT表达式来选择和操控数据的时候非常有用。要记住,在用完表格之后就删除表格。让我再重复一遍:在你用完表格之后,不 要忘记删除表格。
(4)InnoDB和Berkley DB
ISAM和MyISAM数据库引擎不支持事务处理也不支持外来键。尽管要比ISAM和 MyISAM引擎慢很多,但是 InnoDB和BDB包括了对事务处理和外来键的支持。
事务(InnoDB支持)是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
(1) 事务的特征(ACID)
原子性(Atomicity):事务所包含的一系列数据库操作要么全部成功执行,要么全部回滚;
一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态;
隔离性(Isolation):因为数据库允许多个并发十五同时对数据进行读写,隔离性要求并发执行的事务之间不能相互影响,避免多事务并发执行时由于交叉执行导致数据不一致;
持久性(Durability):事务一旦提交,对数据库中数据的改变是永久性的。
(2) 隔离性的4个级别
READ UNCOMMITTED(读未提交):最低级别的隔离,通常又称为dirty read,它允许一个事务读取另一个事务还没commit的数据,这样可能会提高性能,但是会导致脏读问题;
READ COMMITTED(读提交):在一个事务中只允许对其它事务已经commit的记录可见,该隔离级别不能避免不可重复读问题;
REPEATABLE READ(可重复读):在一个事务开始后,其他事务对数据库的修改在本事务中不可见,直到本事务commit或rollback。但是,其他事务的insert/delete操作对该事务是可见的,也就是说,该隔离级别并不能避免幻读问题。在一个事务中重复select的结果一样,除非本事务中update数据库。
SERIALIZABLE(串行化):最高级别的隔离,只允许事务串行执行。
脏读:一个事务读取了另一个事务未提交的数据;
不可重复读:不可重复读的重点是修改,同样条件下两次读取结果不同,也就是说,被读取的数据可以被其它事务修改;
幻读:幻读的重点在于新增或者删除,同样条件下两次读出来的记录数不一样。
MySQL默认的隔离级别是REPEATABLE READ(可重复度)。
(3) mysql的事务支持
MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关:
MyISAM:不支持事务,用于只读程序提高性能;
InnoDB:支持ACID事务、行级锁、并发;
Berkeley DB:支持事务。
MyISAM性能极佳,但却 不支持事务处理。与传统的ISAM、MyISAM相比,InnoDB的最大特色就是支持ACID(事务的四大特性)兼容的事务功能,MyISAM与InnoDB作为MySQL的两大存储引擎的差异主要包括:
MyISAM |
InnoDB | |
构成上的区别: |
每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。 .frm文件存储表定义。 数据文件的扩展名为.MYD (MYData)。 跨平台移植方便 |
InnoDB所有的表都保存在同一个数据文件中,InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB |
事务处理上方面: |
MyISA强调的是性能,其执行速度比InnoDB类型更快,但不提供事务支持 | InnoDB提供事务支持事务,外部键(foreign key)等高级数据库功能 |
SELECT,UPDATE,INSERT,Delete操作 |
执行大量的SELECT,MyISAM是更好的选择 | 1.执行大量的INSER或UPDATE,出于性能方面的考虑应,该使用InnoDB表 2.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。 3.LOAD TABLE FROM MASTER 操作对InnoDB不起作用,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特(例如外键)的表不适用 |
对AUTO_INCREMENT的操作 |
在MyISAM中,可以和其他字段一起建立联合索引。引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,它可以根据前面几列进行排序后递增。 | InnoDB中必须包含只有该字段的索引,并且引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。 |
表的具体行数 |
select count(*) from table,MyISAM只要简单的读出保存好的行数,注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的 | InnoDB 中不保存表的具体行数,执行select count(*) from table时,要扫描一遍整个表来计算有多少行 |
锁 |
提供表级锁,用户在操作MyISAM表时,select、update、delete和insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。 | 提供行级锁,InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。 |
表主键:MyISAM允许没有任何索引和主键的表存在,索引都是保存行的地址。对于InnoDB,如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。全文索引:MyISAM支持 FULLTEXT类型的全文索引;InnoDB不支持FULLTEXT类型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。
外键:MyISAM不支持外键,而InnoDB支持外键。
B*-tree是B+-tree的变体,在B+树的基础上(所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针),B*树中非根和非叶子结点再增加指向兄弟的指针;B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)。给出了一个简单实例,如下图所示:
B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。
B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。
所以,B*树分配新结点的概率比B+树要低,空间使用率更高;
MySQL数据库存在多种数据存储引擎,每种存储引擎的锁定机制也有较大区别。总的来说,MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。
对于一般的用户而言,通过系统的自动锁定管理机制基本可以满足使用要求。 但是涉及到写操作,还是一定要理解隔离机制和并发可能带来的问题,在事务中或者SQL中加入锁机制。对于数据库的死锁,一般数据库系统都会有一套机制去解锁,一般不会造成数据库的瘫痪,但解锁的过程会造成数据库性能的急速下降,反映到程序上就会造成程序的反应性能的下降,并且会造成程序有的操作失败。
(1)快照读VS当前读
多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) 最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,这也是为什么现阶段几乎所有的RDBMS都支持了MVCC。
与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control。
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析)
select * from table where ?;
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。注:这个语句的加锁是数据库完成的。
(2)当前读的加锁
updata操作,在数据库中的执行流程:
从图中,可以看到,一个Update操作的具体流程。当Update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条满足条件的记录,然后InnoDB引擎会将第一条记录返回,并加锁 (current read)。待MySQL Server收到这条加锁的记录之后,会再发起一个Update请求,更新这条记录。一条记录操作完成,再读取下一条记录,直至没有满足条件的记录为止。因此,Update操作内部,就包含了一个当前读。同理,Delete操作也一样。Insert操作会稍微有些不同,简单来说,就是Insert操作可能会触发Unique Key的冲突检查,也会进行一个当前读。
注:根据上图的交互,针对一条当前读的SQL语句,InnoDB与MySQL Server的交互,是一条一条进行的,因此,加锁也是一条一条进行的。先对一条满足条件的记录加锁,返回给MySQL Server,做一些DML操作;然后在读取下一条加锁,直至读取完毕。
传统RDBMS加锁的一个原则,就是2PL (二阶段锁):Two-Phase Locking。相对而言,2PL比较容易理解,说的是锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。下面,仍旧以MySQL为例,来简单看看2PL在MySQL中的实现。
从上图可以看出,2PL就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁。