第一范式 列都是不可再分
第二范式 满足第一范式,并且表中非主键列不存在对主键的部分依赖。
第三范式 满足第二范式,非主键列只依赖于主键,不依赖于其他非主键。
索引的实现通常使用B_TREE。B_TREE索引加速了数据访问,因为存储引擎不会再去扫描整张表得到需要的数据;相反,它从根节点开始,根节点保存了子节点的指针,存储引擎会根据指针快速寻找数据。
Hash索引
mysql中,只有Memory(Memory表只存在内存中,断电会消失,适用于临时表)存储引擎显示支持Hash索引,是Memory表的默认索引类型,尽管Memory表也可以使用B+Tree索引。Hash索引把数据以hash形式组织起来,因此当查找某一条记录的时候,速度非常快。但是因为hash结构,每个键只对应一个值,而且是散列的方式分布。所以它并不支持范围查找和排序等功能。
B+Tree索引
B+Tree是mysql使用最频繁的一个索引数据结构,是Inodb和Myisam存储引擎模式的索引类型。相对Hash索引,B+Tree在查找单条记录的速度比不上Hash索引,但是因为更适合排序等操作,所以它更受欢迎。毕竟不可能只对数据库进行单条记录的操作。
普通索引:最基本的索引,没有任何限制
唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。
主键索引:它是一种特殊的唯一索引,不允许有空值。
全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时耗空间。
组合索引:为了更多的提高mysql效率可建立组合索引。
InnoDb引擎
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数;
MyISAM引擎
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作是对整个表加锁;
存储表的总行数;
Memory引擎
全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在mysql重启时会丢失,默认使用HASH索引,检索效率非常高,但不适用于精确查找,主要用于那些内容变化不频繁的代码表
MyISAM:
● 不⽀持事务,但是每次查询都是原⼦的;
● ⽀持表级锁,即每次操作是对整个表加锁;
● 存储表的总⾏数;
● ⼀个MYISAM表有三个⽂件:索引⽂件、表结构⽂件、数据⽂件;
● 采⽤⾮聚集索引,索引⽂件的数据域存储指向数据⽂件的指针。辅索引与主索引基本⼀致,但是辅索引不⽤保证唯⼀性。
InnoDb:
● ⽀持ACID的事务,⽀持事务的四种隔离级别;
● ⽀持⾏级锁及外键约束:因此可以⽀持写并发;
● 不存储总⾏数;
● ⼀个InnoDb引擎存储在⼀个⽂件空间(共享表空间,表⼤⼩不受操作系统控制,⼀个表可能分布在多个⽂件⾥),也有可能为多个(设置为独⽴表空,表⼤⼩受操作系统⽂件⼤⼩限制,⼀般为2G),受操作系统⽂件⼤⼩的限制;
● 主键索引采⽤聚集索引(索引的数据域存储数据⽂件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使⽤⾃增主键,防⽌插⼊数据时,为维持B+树结构,⽂件的⼤调整。
聚簇索引:将数据存储与索引放到了⼀块、并且是按照⼀定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是⼀致的,即:只要索引是相邻的,那么对应的数据⼀定也是相邻地存放在磁盘上的
⾮聚簇索引:叶⼦节点不存储数据、存储的是数据⾏地址,也就是说根据索引查找到数据⾏的位置再取磁盘查找数据,这个就有点类似⼀本树的⽬录,⽐如我们要找第三章第⼀节,那我们先在这个⽬录⾥⾯找,找到对应的⻚码后再去对应的⻚码看⽂章。
当⼀个SQL想要利⽤索引是,就⼀定要提供该索引所对应的字段中最左边的字段,也就是排在最前⾯的字段,⽐如针对a,b,c三个字段建⽴了⼀个联合索引,那么在写⼀个sql时就⼀定要提供a字段的条件,这样才能⽤到联合索引,这是由于在建⽴a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段从左往右去⽐较⼤⼩进⾏排序的,所以如果想要利⽤B+树进⾏快速查找也得符合这个规则
B树的特点:
1. 节点排序,⼀个节点了可以存多个元素,多个元素也排序
2. 所有的叶子节点在同一层,每个节点上都存储的是key和他对应的数据
B+树的特点:
1. 拥有B树的特点,是在BTree基础上的一种优化
2. 叶⼦节点之间有指针连接
3. 非叶子节点存储key,叶子节点存储key和数据,也就是叶⼦节点中存储了所有的元素,并且排好顺序
(一)可读取未提交(Read uncommitted)
写事务阻止其他写事务,避免了更新遗失。但是没有阻止其他读事务。
存在的问题:脏读。即读取到不正确的数据,因为另一个事务可能还没提交最终数据,这个读事务就读取了中途的数据,这个数据可能是不正确的。可能会导致脏读、幻读或不可重复读。
解决办法就是下面的“可读取确认”。
(二)可读已提交(Read committed)
Sql Server , Oracle的默认隔离级别
写事务会阻止其他读写事务。读事务不会阻止其他任何事务。
存在的问题:不可重复读。即在一次事务之间,进行了两次读取,但是结果不一样,可能第一次id为1的人叫“李三”,第二次读id为1的人就叫了“李四”。因为读取操作不会阻止其他事务。
解决办法就是下面的“可重复读”。可以阻止脏读,但是幻读或不可重复读仍有可能发生。
(三)可重复读(Repeatable read)
MySQL的默认隔离级别
读事务会阻止其他写事务,但是不会阻止其他读事务。
存在的问题:幻读。可重复读阻止的写事务包括update和delete(只给存在的表加上了锁),但是不包括insert(新行不存在,所以没有办法加锁),所以一个事务第一次读取可能读取到了10条记录,但是第二次可能读取到11条,这就是幻读。
解决办法就是下面的“串行化”。可以阻止脏读和不可重复读,但幻读仍有可能发生。
(四)可串行化(Serializable)
可避免幻读。读加共享锁,写加排他锁。这样读取事务可以并发,但是读写,写写事务之间都是互斥的,基本上就是一个个执行事务。//最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
脏读:也就是当数据库的一个事务A正在使用一个数据但还没有提交,另外一个事务B也访问到了这个数据,还使用了这个数据,这就会导致事务B使用了事务A没有提交之前的数据。
不可重复读:在一个事务A中多次操作一个数据,在这两次或多次访问这个数据的中间,事务B也操作此数据,并使其值发生了改变,这就导致同一个事务A在两次操作这个数据的时候值不一样,这就是不可重复读。
幻读:是指事务不独立执行产生的一种现象。事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。这样就会导致当A本来执行的结果包含B执行的结果,这两个本来是不相关的,对于A来说就相当于产生了“幻觉”。
按锁粒度分类:
1. ⾏锁:锁某⾏数据,锁粒度最⼩,并发度⾼
2. 表锁:锁整张表,锁粒度最⼤,并发度低
3. 间隙锁:锁的是⼀个区间
还可以分为:
1. 共享锁:也就是读锁,⼀个事务给某⾏数据加了读锁,其他事务也可以读,但是不能写
2. 排它锁:也就是写锁,⼀个事务给某⾏数据加了写锁,其他事务不能读,也不能写
还可以分为:
1. 乐观锁:并不会真正的去锁某⾏记录,⽽是通过⼀个版本号来实现的
2. 悲观锁:上⾯所的⾏锁、表锁等都是悲观锁
1. 当只要一行数据时使用LIMIT1 ,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。
2. 避免出现SELECT * FROM table 语句,要明确查出的字段。
3. 在一个SQL语句中,如果一个where条件过滤的数据库记录越多,定位越准确,则该where条件越应该前移。
4. 查询时尽可能使用索引覆盖。即对SELECT的字段建立复合索引,这样查询时只进行索引扫描,不读取数据块。
5. 在判断有无符合条件的记录时建议不要用SELECT COUNT (*)和select top 1 语句。
6. 尽量减少子查询,使用关联查询(left join,right join,inner join)替代
7. 应绝对避免在order by子句中使用表达式。
8. 如果需要从关联表读数据,关联的表一般不要超过7个。
9. 小心使用 IN 和 OR,需要注意In集合中的数据量。建议集合中的数据不超过200个。
10. <> 用 < 、 > 代替,>用>=代替,<用<=代替,这样可以有效的利用索引。
11. 在查询时尽量减少对多余数据的读取包括多余的列与多余的行。
1. 检查是否⾛了索引,如果没有则优化SQL利⽤索引
2. 检查所利⽤的索引,是否是最优索引
3. 检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据
4. 检查表中数据是否过多,是否应该进⾏分库分表了
5. 检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源
多版本并发控制:读取数据时通过⼀种类似快照的⽅式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到⾃⼰特定版本的数据,版本链
垂直拆分:拆出来的每个表,每个表的表结构不同,表数据不同,每个表的字段至少有一列是交集,所有数据就是这些表的并集
水平拆分:拆出来的每个表,结构是相同的,数据是不同的,
要选择合适的策略:大部分数据是和用户相关联的,所以我们用户id是常用的分表字段,假设我们有order表,由于order表记录条数太多,将被拆分成256张表。拆分的记录根据user_id%256取得对应的表进行存储,
分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库服务器就会无法承载写操作压力,我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库!与分表策略相似,分库可以采用通过一个关键字取模的方式,来对数据访问进行路由。还是之前的订单表,假设user_id 字段的值为258,将原有的单库分为256个库,那么应用程序对数据库的访问请求将被路由到第二个库(258%256 = 2)。
有时数据库可能既面临着高并发访问的压力,又需要面对海量数据的存储问题,这时需要对数据库既采用分表策略,又采用分库策略,以便同时扩展系统的并发处理能力,以及提升单表的查询性能,这就是所谓的分库分表。
分库分表的策略比前面的仅分库或者仅分表的策略要更为复杂,一种分库分表的路由策略如下:
◆中间变量 = user_id % (分库数量 * 每个库的表数量)
◆库 = 取整数 (中间变量 / 每个库的表数量)
◆表 = 中间变量 % 每个库的表数量
同样采用user_id作为路由字段,首先使用user_id 对库数量*每个库表的数量取模,得到一个中间变量;然后使用中间变量除以每个库表的数量,取整,便得到对应的库;而中间变量对每个库表的数量取模,即得到对应的表。
内联接(Inner Join):匹配2张表中相关联的记录。
左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。
右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。在判定左表和右表时,要根据表名出现在Outer Join的左右位置关系。