Sql数据库原理(B+树)及相关问题

  • 1、数据库范式:对数据库规范化设计,减少数据冗余,增加数据一致性
    • 第一范式:列不可分、无重复列,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。
    • BCNF范式和第四范式
  • 2、磁盘读取
    • 磁盘读取数据I/O:寻找磁道+旋转选择
    • 预读+局部性原理:
      • 页存储模型:磁盘I/O读取非常慢,根据数据预读原理,当磁盘一个数据被查找使用,计算机将顺序读取一定长度的数据放入内存(页的整倍数,一般为16K,innoDB其中一页同样为16k)——局部性原理:当一个数据被用到时,其附近的数据通常也马上会被用到;
      • 页的构成:
        • 最小虚记录和最大虚记录:页存储数据范围
        • 记录堆,索引数据存储区域
        • slot区:页面有效指针,存储记录相对页面首地址的偏移(快速定位到数据在那个Slot中进行快速检索)
        • 页尾:页面校验信息
      • InnoDB存储结构:
        • 段:数据段、索引段、回滚段
        • 区:连续页组成,1MB,一个区64页,InnoDB一次会申请4-5个区
        • 页:InnoDB最小的存储单位,16K
        • 行:面向行的引擎
  • 3、索引:建立索引就是要进行排序( 叶子节点:数据;非叶子几点:主键id+指针———8b+6b=14b)
    • B+数高度=2,1条数据为1kb,一页16kb,1个叶子节点可以存放16条数据。一个非叶子节点16kb/14b=1170;1170*16=18724条数据 ;
    • B+高度=3,1170*1170*16=21907740条。大于2千万条数据就需要分表,最多进行2次磁盘Io
    • innoDB的最小储存单位页 4kb(16384)
    • 页的Fil Header(指向下一页的指针和指向前一页的指针)
    • 存储一个表中的一行

    • Page Directory(页目录):主键要小,自增
      • 红色部分为主键(也可以理解为页目录)主键索引

        Sql数据库原理(B+树)及相关问题_第1张图片

      • 联合索引(红色仍为主键,但其叶子节点只包含联合排序+主键。如需其他行信息需要利用主键索引进行查找)

        Sql数据库原理(B+树)及相关问题_第2张图片

  • 4、数据库索引
    • JDBC连接数据库,进入连接池,SQL接口 ——解析器(编译,底层可执行代码)——优化器——缓存(热点数据缓冲)——存储引擎innoDB

      Sql数据库原理(B+树)及相关问题_第3张图片

    • 索引是对数据库表中一个或多个列的值进行排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B_TREE及其变种。索引加速了数据访问,因为存储引擎不会再去扫描整张表得到需要的数据;相反,它从根节点开始,根节点保存了子节点的指针,存储引擎会根据指针快速寻找数据。
    • B-Tree平衡多路查找树

      Sql数据库原理(B+树)及相关问题_第4张图片

      • 度(Degree)-节点的数据存储个数
      • 叶节点具有相同的深度
      • 叶节点的指针为空
      • 叶节点中的数据key从左到右递增排列
    • B+Tree:非叶节点不存储data,只存key,可以增大度;叶节点不存指针;叶节点存在顺序访问指针,提高区间访问的性能
      • 每个Node都是一页
      • B-Tree:6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4

      • B+Tree

      • B+树是对B树的一种变形树,它与B树的差异在于:
        • B+树的非叶子结点只包含导航信息,具有索引作用,不包含实际的值,跟记录有关的信息均存放在叶结点中
        • 所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历,可以按照关键码排序的次序遍历全部记录。
      • B+ 树的优点在于:
        • 由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
        • B+树的叶子结点都是相链的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
        • 但是B树也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。
    • B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引
      • B+tree的磁盘读写代价更低:B+tree的内部结点并没有指向关键字具体信息的指针(红色部分),因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多,相对来说IO读写次数也就降低了;
      • B+tree的查询效率更加稳定:由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引,所以,任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当;
      • 数据库索引采用B+树而不是B树的主要原因:B+树只要遍历叶子节点就可以实现整棵树的遍历,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低。
      • 文件与数据库都是需要较大的存储,也就是说,它们都不可能全部存储在内存中,故需要存储到磁盘上。而所谓索引,则为了数据的快速定位与查找,那么索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,因此B+树相比B树更为合适。数据库系统巧妙利用了局部性原理与磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入,而红黑树这种结构,高度明显要深的多,并且由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性。最重要的是,B+树还有一个最大的好处:方便扫库。B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query非常方便,而B树不支持,这是数据库选用B+树的最主要原因。
    • MyISAM索引实现(非聚集)
      • 索引文件和数据文件分离:.frm结构文件 .MYD数据文件 .MTI索引文件
      • B+tree主键索引和非主键页节点存储数据为指针,
    • InnoDB索引实现(聚集)
      • 索引文件和数据文件结合:.frm结构文件 .ibd数据文件+索引文件
      • B+tree主键索引下会存储此行的所有信息,非主键索引页节点存储数据为主键索页结点
    • 联合索引:最右原则
  • 4、数据库事务:事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
    • (1). 事务的特征
      • 原子性(Atomicity):事务所包含的一系列数据库操作要么全部成功执行,要么全部回滚;
      • 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态;
      • 隔离性(Isolation):并发执行的事务之间不能相互影响;
      • 持久性(Durability):事务一旦提交,对数据库中数据的改变是永久性的。
    • (2). 事务并发带来的问题
      • 脏读:一个事务读取了另一个事务未提交的数据;
      • 不可重复读:不可重复读的重点是修改,同样条件下两次读取结果不同,也就是说,被读取的数据可以被其它事务修改;
      • 幻读:幻读的重点在于新增或者删除,同样条件下两次读出来的记录数不一样。
    • (3). 隔离级别
      • 隔离级别决定了一个session中的事务可能对另一个session中的事务的影响。ANSI标准定义了4个隔离级别,MySQL的InnoDB都支持,分别是:
        • READ UNCOMMITTED:最低级别的隔离,通常又称为dirty read,它允许一个事务读取另一个事务还没commit的数据,这样可能会提高性能,但是会导致脏读问题;
        • READ COMMITTED:在一个事务中只允许对其它事务已经commit的记录可见,该隔离级别不能避免不可重复读问题;
        • REPEATABLE READ:在一个事务开始后,其他事务对数据库的修改在本事务中不可见,直到本事务commit或rollback。但是,其他事务的insert/delete操作对该事务是可见的,也就是说,该隔离级别并不能避免幻读问题。在一个事务中重复select的结果一样,除非本事务中update数据库。
        • SERIALIZABLE:最高级别的隔离,只允许事务串行执行。
        • MySQL默认的隔离级别是REPEATABLE READ。
      • (4)、mysql的事务支持:MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关:
        • MyISAM:不支持事务,用于只读程序提高性能;
        • InnoDB:支持ACID事务、行级锁、并发;
        • Berkeley DB:支持事务
  • 5、实践中如何优化MySQL:实践中,MySQL的优化主要涉及SQL语句及索引的优化、数据表结构的优化、系统配置的优化和硬件的优化四个方面,如下图所示:

    Sql数据库原理(B+树)及相关问题_第5张图片

    • 1)、SQL语句及索引的优化
      • (1). SQL语句的优化:
        • a. 怎么发现有问题的SQL?(通过MySQL慢查询日志对有效率问题的SQL进行监控):MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10s以上的语句。通过MySQL的慢查询日志,我们可以查询出执行的次数多占用的时间长的SQL、可以通过pt_query_disgest(一种mysql慢日志分析工具)分析Rows examine(MySQL执行器需要检查的行数)项去找出IO大的SQL以及发现未命中索引的SQL,对于这些SQL,都是我们优化的对象。
        • b. 通过explain查询和分析SQL的执行计划:使用 EXPLAIN 关键字可以知道MySQL是如何处理你的SQL语句的,以便分析查询语句或是表结构的性能瓶颈。通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用以及每张表有多少行被优化器查询等问题。当扩展列extra出现Using filesort和Using temporay,则往往表示SQL需要优化了。

          Sql数据库原理(B+树)及相关问题_第6张图片

        • c. SQL语句的优化
          • 优化insert语句:一次插入多值;
          • 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描;
          • 应尽量避免在 where 子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描;
          • 优化嵌套查询:子查询可以被更有效率的连接(Join)替代;
          • 很多时候用 exists 代替 in 是一个好的选择。
      • (2)、索引优化
        • 建议在经常作查询选择的字段、经常作表连接的字段以及经常出现在order by、group by、distinct 后面的字段中建立索引。但必须注意以下几种可能会引起索引失效的情形:
          • 以“%(表示任意0个或多个字符)”开头的LIKE语句,模糊匹配;
          • OR语句前后没有同时使用索引;
          • 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型);
          • 对于多列索引,必须满足最左匹配原则(eg,多列索引col1、col2和col3,则 索引生效的情形包括col1或col1,col2或col1,col2,col3)。
    • 2). 数据库表结构的优化:数据库表结构的优化包括选择合适数据类型、表的范式的优化、表的垂直拆分和表的水平拆分等手段。
      • (1). 选择合适数据类型
        • 使用较小的数据类型解决问题;
        • 尽可能的使用not null 定义字段;
        • 使用简单的数据类型(mysql处理int要比varchar容易);
        • 尽量避免使用text类型,非用不可时最好考虑分表;
      • (2). 表的范式的优化:一般情况下,表的设计应该遵循三大范式。
      • (3). 表的垂直拆分和水平拆分:
        • 垂直拆分就是要把表按模块划分到不同数据库表中(当然原则还是不破坏第三范式),这种拆分在大型网站的演变过程中是很常见的。当一个网站还在很小的时候,只有小量的人来开发和维护,各模块和表都在一起,当网站不断丰富和壮大的时候,也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。其实,相对于垂直切分更进一步的是服务化改造,说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程,最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也非常有利于进行局部的优化和治理,保障核心模块的稳定性。垂直拆分:单表大数据量依然存在性能瓶颈
        • 水平拆分,上面谈到垂直切分只是把表按模块划分到不同数据库,但没有解决单表大数据量的问题,而水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。例如像计费系统,通过按时间来划分表就比较合适,因为系统都是处理某一时间段的数据。而像SaaS应用,通过按用户维度来划分数据比较合适,因为用户与用户之间的隔离的,一般不存在处理多个用户数据的情况,简单的按user_id范围来水平切分。
        • 通俗理解:水平拆分行,行数据拆分到不同表中, 垂直拆分列,表数据拆分到不同表中。
    • 3). 系统配置的优化
      • 操作系统配置的优化:增加TCP支持的队列数
      • mysql配置文件优化:Innodb缓存池设置(innodb_buffer_pool_size,推荐总内存的75%)和缓存池的个数(innodb_buffer_pool_instances)
    • 4). 硬件的优化
      • CPU:核心数多并且主频高的
      • 内存:增大内存
      • 磁盘配置和选择:磁盘性能
  • 六、MySQL中的悲观锁与乐观锁的实现:
    • 悲观锁与乐观锁的应用场景
      • 一般情况下,读多写少更适合用乐观锁,读少写多更适合用悲观锁。乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能。
    • 悲观锁
      • 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
      • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
      • Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
    • 乐观锁
      • 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
      • 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
      • 乐观锁一般来说有以下2种方式:
        • 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
        • 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
  • 七、MySQL存储引擎中的MyISAM和InnoDB区别详解
    • 在MySQL 5.5之前,MyISAM是mysql的默认数据库引擎,其由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良。虽然MyISAM性能极佳,但却有一个显著的缺点: 不支持事务处理。不过,MySQL也导入了另一种数据库引擎InnoDB,以强化参考完整性与并发违规处理机制,后来就逐渐取代MyISAM。
    • InnoDB是MySQL的数据库引擎之一,其由Innobase oy公司所开发,2006年五月由甲骨文公司并购。与传统的ISAM、MyISAM相比,InnoDB的最大特色就是支持ACID兼容的事务功能,类似于PostgreSQL。目前InnoDB采用双轨制授权,一是GPL授权,另一是专有软件授权。具体地,MyISAM与InnoDB作为MySQL的两大存储引擎的差异主要包括:
      • 存储结构:每个MyISAM在磁盘上存储成三个文件:第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义,数据文件的扩展名为.MYD (MYData),索引文件的扩展名是.MYI (MYIndex)。InnoDB所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。
      • 存储空间:MyISAM可被压缩,占据的存储空间较小,支持静态表、动态表、压缩表三种不同的存储格式。InnoDB需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。
      • 可移植性、备份及恢复:MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便,同时在备份和恢复时也可单独针对某个表进行操作。InnoDB免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了。
      • 事务支持:MyISAM强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。InnoDB提供事务、外键等高级数据库功能,具有事务提交、回滚和崩溃修复能力。
      • AUTO_INCREMENT:在MyISAM中,可以和其他字段一起建立联合索引。引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,它可以根据前面几列进行排序后递增。InnoDB中必须包含只有该字段的索引,并且引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。
      • 表锁差异:MyISAM只支持表级锁,用户在操作MyISAM表时,select、update、delete和insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。InnoDB支持事务和行级锁。行锁大幅度提高了多用户并发操作的新能,但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。
      • 全文索引:MyISAM支持 FULLTEXT类型的全文索引;InnoDB不支持FULLTEXT类型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。
      • 表主键:MyISAM允许没有任何索引和主键的表存在,索引都是保存行的地址。对于InnoDB,如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。
      • 表的具体行数:MyISAM保存表的总行数,select count() from table;会直接取出出该值;而InnoDB没有保存表的总行数,如果使用select count() from table;就会遍历整个表,消耗相当大,但是在加了wehre条件后,myisam和innodb处理的方式都一样。
      • CURD操作:在MyISAM中,如果执行大量的SELECT,MyISAM是更好的选择。对于InnoDB,如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。DELETE从性能上InnoDB更优,但DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。
      • 外键:MyISAM不支持外键,而InnoDB支持外键。
      • 通过上述的分析,基本上可以考虑使用InnoDB来替代MyISAM引擎了,原因是InnoDB自身很多良好的特点,比如事务支持、存储过程、视图、行级锁、外键等等。尤其在并发很多的情况下,相信InnoDB的表现肯定要比MyISAM强很多。另外,必须需要注意的是,任何一种表都不是万能的,合适的才是最好的,才能最大的发挥MySQL的性能优势。如果是不复杂的、非关键的Web应用,还是可以继续考虑MyISAM的,这个具体情况具体考虑。

你可能感兴趣的:(数据库)