第一范式1NF:
数据表中的每一列(字段),必须是不可拆分的最小单元,也就是确保每一列的原子性,而不是集合。
第二范式2NF:
满足1NF的基础上,要求:表中的所有列,都必需依赖于主键,而不能有任何一列与主键没有关系(一个表只描述一件事情)。第二范式消除表的无关数据。
第三范式3NF:
满足2NF的基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)(也表明不允许数据存在冗余的现象)
1、主键约束(Primay Key)
唯一性,非空性
2、唯一约束 (Unique)
唯一性,可以空,但只能有一个
3、检查约束 (Check)
对该列数据的范围、格式的限制(如:年龄、性别等)
4、默认约束 (Default)
该数据的默认值
5、外键约束 (Foreign Key)
需要建立两表间的关系
1、原子性(Atomicity)
原子性是指事务包含的所有 一系列操作 要么全部成功提交,要么全部失败回滚。它也是数据库事务最最本质的特性。
比如一个事务包含两个更新操作A和B,A操作更新成功,而B操作更新失败,则A操作会被回滚。绝不会出现一个成功、一个失败的场景,否则这不是一个事务。
2、一致性(Consistency)
一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。这里有个容易混淆点,容易和数据一致性混淆。这里更多的强调的是单机下的事务一致性,必须是一个事务内部。
保证数据库一致性的主要机制层面:数据库机制层面、业务层面。
3、隔离性(Isolation )
每个事务都有各自的资源单位,事务与事务之间是互相隔离的、不可见的,而事务的结果是对其他事务可见的。
一个数据库系统中存在多个事务,每个事务中的各个子操作是对其他事务不可见的,在提交后的结果是对其他事务可见的。可以理解为资源粒子度的划分与隔离。
4、持久性(Durability )
持久性确保的是一旦提交了事务,即使出现系统故障,该事务的更新也不会丢失。我们可以宽泛的认为,将数据持久化到磁盘。
隔离级别由低到高分别为
1、读未提交(READ_UNCOMMITTED)
就是一个事务A读取另一个事务B 未提交 的数据。(如果如果事务B出现 回滚,那么事务A就会出现 脏读❌ 的问题)。
2、读已提交(READ_COMMITTED)
一个事务A读取另一个事务B 已提交 的数据,那么这样可以解决 脏读 的问题。保证读取的数据 已提交✅ 而不能被 回滚。在事务B提交前的 update,事务A是读取不到的。只有事务B提交后,事务A才能读取到事务B的 update改动。出现的问题是 一个事务范围内两个相同的查询却返回了不同的数据,那么这就是不可重复读。
3、可重复读(REPETABLE_READ)
事务开启时,不再允许其他事务 修改(update) 数据。这样就可以无限制的读取没有被 修改(update) 的数据了。出现的问题是,当有 并行插入(insert) 操作时就会出现 幻读。
4、可串行化(SERIALIZABLE)
在可串行化的隔离级别下,将事务 串行化 顺序执行。那么事务不能进行并行操作,也就解决了 幻读 的问题。
InnoDB 的默认事务隔离级别是:可重复读(REPETABLE_READ)。
B树(B-TREE)满足如下条件,即可称之为m阶B树:
1、每个结点最多拥有m棵子树;
2、根结点最少拥有2颗子树(存在子树的情况下);
3、除了根结点以外,其余每个分支结点至少拥有 m/2 棵子树;
4、所有的叶结点都在同一层上;
5、有 k 棵子树的分支结点则存在 k-1 个关键码,关键码按照递增次序进行排列;
6、关键字数量需要满足ceil(m/2)-1 <= n <= m-1;
B-tree的特点是每个结点不仅存放键值,而且存放数据。
B+树特点:
1、所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3、所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
B+树的优点:
1、单一节点存储更多的元素(因为不含有对应的值,仅仅含有键),使得查询的IO次数更少。
2、所有查询都要从根节点查找到叶子节点,查询性能稳定,相对于B树更加稳定,以为B+树只有叶子节点存储了对应的值信息。
3、所有叶子节点形成有序双向链表,对于SQL的范围查询以及排序查询都很方便。
4、B/B+树的共同优点的每个节点有更多的孩子,插入不需要改变树的高度,从而减少重新平衡的次数,非常适合做数据库索引这种需要持久化在磁盘,同时需要大量查询和插入的应用。树中节点存储这指向页的信息,可以快速定位到磁盘对应的页上面。
平衡二叉树(Balanced Binary Tree)的 平衡性 体现在以下几点:
1、树的左、右子树的 高度差 的绝对值不超过1;
2、任意节点左、右子树也分别平衡二叉树;
一棵数频繁的删除和插入节点都有可能使数发生旋转。平衡二叉树中访问节点的操作(插入、查找、删除)的时间复杂度维持在一个“平衡”的水准,既最坏情况和最好情况的时间复杂度维持在 O(logN)。
二叉查找树(二叉搜索树 Binary Search Tree)的 查找 特性体现在以下几点:
1、如果它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
2、如果它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
3、任意节点左、右子树也分别为二叉查找树。
二叉查找树的查找过程
1、从树的根节点开始查找,并沿着这棵树中的一条简单路径向下进行;
2、若树为空树,则查找失败,返回 null;
3、对于访问的每个节点 x,若指定的值 k 等于结点 x 的值,返回该节点x 并结束查找;
4、若指定的值 k 小于节点 x 的值,则在节点 x 的左子树中查找(根据上面二叉查找树的性质判断);
5、相反,若指定的值 k 大于节点 x 的值,则查找在节点 x 的右子树中继续;
6、若查找至 叶子节点 后仍未匹配到相等值,则表示不存在指定值的节点,返回null。
红黑树(Red Black Tree) 也是一种 平衡二叉树(而平衡二叉树又是一种二叉查找树,所以红黑树也是一种二叉查找树),但它不像 AVL树 那样追求绝对的平衡,它这种于查找和添加/删除之间。特性是:
1、树中所有节点都被染成红色或黑色;
2、根节点和叶子节点都是黑色;
3、每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点);
4、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
上述特性构成了一个最关键的约束:从根节点到叶子节点的最长路径不大于最短路径的两倍长。
最差平衡二叉树即树退化成链表(可以看成特殊的树、没有叉的树),这样的树添加/删除元素快,不需要自平衡,但是查找慢,最坏情况要遍历整棵树。而从增删改查的时间复杂度来讲,红黑树 介于 二叉查找树 和 链表 之间,这是在添加/删除和查找之间一个折中的选择。
锁不仅是资源占有的一种处理机制,更是多线程或并发编程下对数据一致性的一种保证。加锁和释放锁本身也会消耗资源。了解并合理利用锁机制,能大大提升数据库的性能。
一个事务对数据加共享锁,也可以允许其他事务对此交集数据加此锁。但阻止其他事务对此交集数据加排他锁。
一个事务对数据加排他锁,会阻止其他事务对此交集数据加任何锁。
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),在这里的两种意向锁都是表锁(绕脑)。
事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
每个事务操作会锁住整张表,粒子度最大,简单粗暴。优点是加锁和释放锁次数会大大减少。缺点是锁冲突的概率会大大增加,高并发情况下不可取。
资源开销介于行级锁和表级锁,会出现死锁。
每个事务仅会锁住被影响的行,也就是说,涉及到哪些行记录,哪些行才会被锁住,会出现死锁。优点是锁冲突概率小,并发度高。缺点是由于锁离子度小,加锁和释放锁的次数会大大增加,资源开销大。
MySQL的行级锁通过索引上的索引项来实现的,InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁。
当我们用范围条件而不是相等条件检索数据,并请求共享锁或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做 “间隙(GAP)” ,InnoDB也会对这个“间隙”加锁,这种锁机制不是所谓的 间隙锁(Next-Key锁)。
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁。
1、为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个记录(行)使用SELECT … FOR UPDATE语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。
2、在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁
3、如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会
4、通过SELECT … LOCK IN SHARE MODE获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。
5、改变事务隔离级别
如果出现死锁,可以用 SHOW INNODB STATUS 命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。
加锁是为了占用资源,我们上面说过加锁和释放锁都会有资源开销。在有些不需要加锁就能获取资源岂不是更好?。乐观锁是乐观的认为在抢占资源时不用加锁就能获取资源(因为没有其他事务抢占资源或者发生的冲突概率小,稍稍尝试几次就能成功,美滋滋)。适用于冲突概率小的情景下。
在冲突概率大的情况下,悲观的认为抢不到资源或者多次都抢不到资源。只能通过加锁的方式的抢占资源,然后再做处理,最后释放资源。
应用时以读和插入操作为主,只有少量的更新和删除,并且对事务的完整性,并发性要求不是很高的
事务处理,以及并发条件下要求数据的一致性。除了插入和查询外,包括很多的更新和删除。(InnoDB 有效地降低删除和更新导致的锁定)
1、 禁止无边界范围查询 != 、< 、> 、=< 、>= ,否则不会命中索引。
2、 禁止无边界范围查询 NOT IN ,否则不会命中索引。在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数。
3、 禁止‘%abc’或‘%abc%’查询,否则不会命中索引
4、 尽量使用 EXISTS 代替 select count(1) 来判断是否存在记录。count 函数只有在统计表中所有行数时使用
5、 对于多表 JOIN 时的 ON 条件中字段类型一定要一致,否则也不会命中索引
6、 避免在 WHERE 子句中对字段进行表达式操作,不会命中索引
7、 当只要一行数据时使用 LIMIT 1
8、 多使用别名,别名是大型数据库的应用技巧,可以减少解析的时间并减少那些由 Column 歧义引起的语法错误
9、 最好不要使用返回所有:select from t ,用具体的字段列表代替 “*”,不要返回用不到的任何字段。
10、 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
11、 提高 GROUP BY 语句的效率,可以通过将不需要的记录在 GROUP BY 之前过滤掉。
SELECT JOB, AVG(SAL) FROM EMP GROUP BY JOB HAVING JOB = ‘PRESIDENT’ OR JOB = ‘MANAGER’
SELECT JOB, AVG(SAL) FROM EMPWHERE JOB = ‘PRESIDENT’ OR JOB = ‘MANAGER’ GROUP BY JOB
1、 字段的默认值不要为 null,否则不会命中索引
2、 小数类型使用 decimal,禁止使用 float 与 double。float 与 double存储数据时,可能会损失精度,进而判断的时候导致结果不准,强制 使用 decimal 数据类型。
3、 表达是否的概念时,字段使用is_开头,数据类型使用 unsigned tinyint类型,1表示是,0表示否
4、 有时候是不需要建索引:性别字段、状态,这种不同值很少的字段是不需要建索引的。
5、 单表行数超过 500万行 或者单表容量超过 2G,才推荐分表
6、 在 WHERE 及 ORDER BY 涉及的列上建立索引。
7、 创建表时使用 NOT NULL,或者使用一个特殊的值,如 0,-1 作为默认值。
8、 使用“临时表”暂存中间结果,可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。
1、 进行 UPDATE 或 DELETE 时,必先 SELETE,避免出现误删数据
2、 当有一批处理的插入或更新时,用批量插入或批量更新,绝不会一条条记录的去更新。
1、 char,varchar,text区别
长度的区别。char范围是0~255,varchar最长是64k,text最大能到4G;
效率来说基本是char>varchar>text
char和varchar可以有默认值,text不能指定默认值
2、 当服务器的内存够多时,配制线程数量 = 最大连接数+5,这样能发挥最大的效率;否则使用配制线程数量< 最大连接数,启用 SQL SERVER 的线程池来解决,如果还是数量 = 最大连接数+5,严重的损害服务器的性能。
3、不要有超过 5 个以上的表连接(JOIN),考虑使用临时表或表变量存放中间结果。少用子查询,视图嵌套不要过深,一般视图嵌套不要超过 2 个为宜。
4、 聚簇索引和非聚簇索引
聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
我们日常工作中,根据实际情况自行添加的索引都是辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。
1、 表的主键、外键必须有索引
2、 经常与其他表进行连接的表,在连接字段上应该建立索引
3、 索引应该建在选择性高的字段上
4、 索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引
5、 复合索引的建立需要进行仔细分析,尽量考虑用单字段索引代替
6、 如果复合索引所包含的字段超过 3 个,那么仔细考虑其必要性,考虑减少复合的字段
7、 如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引
8、 频繁进行数据操作的表,不要建立太多的索引
9、 尽量不要对数据库中某个含有大量重复的值的字段建立索引
10、 索引的创建要与应用结合考虑,建议大的 OLTP 表不要超过 6 个索引
11、 要注意索引的维护,周期性重建索引
数据库承载的数据以及请求负载较高时,我们就要考虑使用读写分离、数据缓存。随着业务的增长,数据库的压力达到了承载的阈值,就要考虑分库分表,分解、分摊单个数据库的压力。
数据库的垂直拆分: 通常,我们将所有的数据按照不同的业务建立并存储不同的表(table),垂直拆分是按照业务将一个数据库拆分多个数据库。原来每个业务对应一张表,垂直拆分后,是一个业务对应一个数据库(当然也有坑可能是多个业务对应一个数据库)。其核心是专库专用。达到的结果是将原来一个数据库系统的压力按照业务均摊到各个拆分后的数据库中。垂直拆分也是比较推荐的一直拆分方式。
垂直分片往往需要对架构和设计进行调整。在当前微服务化的进程中,对数据库的垂直拆分是非常友好的。
数据表的垂直拆分: 单表的数据达到2GB或500万行记录就要考虑拆分数据表,垂直拆分表就将热点列和不经常使用的列表拆分开,降低单表的大小。
当一般垂直拆分遇到瓶颈时,会对数据表进行水平拆分。这种方式与垂直拆分不同的地方是,它不会更改表结构。水平分表是将一个表的拆分多结构相同的多个表;水平分库是将一个表拆分成多个结构相同的表,并且这些表分布在不同的数据库。
分布分表中间件有两种:
1、代理模式的分库分表中间件:MyCat;
2、客户端模式的分库分表中间件:ShardingJDBC;
3、支持事务的分布式数据库。
九、 NoSQL与关系数据库的区别
1、SQL 数据存在特定结构的表中;NoSQL 则更加灵活和可扩展,存储方式可以是JSON 文档、哈希表或者其他方式。
2、SQL 中必须定义好表和字段结构后才能添加数据,在NoSQL 中,数据可以在任何时候任何地方添加,不需要先定义表。
3、SQL 中可以使用JOIN将多个关系数据表中的数据用一条简单的查询语句查询出来。NoSQL 无,也正因为此,性能提升。
4、SQL 中不允许删除已经被使用的外部数据,而NoSQL 中可以随时删除任何数据。
5、SQL 中如果多张表数据需要同批次被更新,如果其中一张表更新失败的话其他表也不能更新成功。这种场景可以通过事务来控制。而NoSQL 中没有事务这个概念,每一个数据集的操作都是原子级的。
1、错误日志:记录启动,运行或者停止mysql 时出现的问题
2、通用日志:记录建立的客户端连接和执行的语句
3、二进制日志:记录所有更改数据的语句
4、慢查询日志:记录所有执行时间超过long_query_time 秒的的语句都会被记录。