数据库,你好!

大学时就开始学习数据库,到工做2年多了,接触过不少数据库相关的产品,如Oracle/MySQL这样的RDBMS,Redis/Mongodb这样的NoSQL,还有Hive/HBase这样的大数据技术。虽说招式学了不少,而好的程序员应当知道数据库基本原理,索引基本原理,常见优化手段等。花了近一个月的时间对数据库的这些知识进行学习,收获不少,之前数据库在我眼中就是个增删改查的东西,而如今想法发生了不少改变!程序员


组成数据库的2个要素数据库


wKiom1ajFA-BXSznAAALRpdIvyo206.png


数据库中的数据存储在哪里?缓存

能够是磁盘,或者磁盘组成的阵列,或者内存,或者某些存储设备,总之数据应该有地方能够存储。并发


然而仅仅存储数据是不够的,一块储存有数据的硬盘,可以称之为数据库吗?ide

应该有这样的程序:封装对存储的操做细节,提供给外界简单的API。这样的程序就是实例。高并发


所以,数据库的启动,应当是实例对存储的服务启动,提供给外界简单的API来完成数据操做。性能


那么进一步思考下,存储和实例是什么关系呢?是一对一,又或者是多对多呢?学习


若是,存储有多份,甚至这多份之间还不在一个机房,一个地方,这不就是冗余备份,异地容灾吗?大数据

若是,一个存储对应多个实例,那么将会提升外界对这个存储的负载能力以及达到高可用。一个实例挂了,不用担忧,还有其余的实例来确保这个存储对外界的可用性。优化



数据库的物理与逻辑转换


块的概念


数据库的数据是如何存储的,这很是重要,由于如何存储决定了如何访问数据。

试想,若是按行存储,按行访问,或者咱们采用JAVA相似的随机访问的方式,先定位位置后在读取一段内容,数据库会这样作吗?

行,是抽象的,能够很大,也能够很小,粒度是不可控的,并且行仍是逻辑上的概念,并不利于数据库进行物理上的调度和管理。那么数据库的数据到底如何存储呢?


wKiom1ajIN7iHctMAAAKDNBv8QQ046.png

数据库会对存储进行物理大小的划分(好比16K),叫作Block(或者Page)。


一个Block内会存储不少行,数据库在加载一个Block时,一般并不会仅仅只加载这个Block,而是会将这个Block周围的Block都加载至内存,由于会显著的提高内存命中率以及减小IO次数。为了方便管理,应该要对Block进行编号,以及Block内的行进行编号。(也许编号并非惟一的,可是与某些高层次的编号组合起来就是惟一能够定位了,例如Oracle的rowid就是表空间、对象编号,块编号,块内行编号共同组成,所以根据rowid就能够快速定位到数据。)


要知道在文件系统上,并无Block的概念,Block不过是数据库自认为的东西,那么数据库如何定位以及读取一个Block呢?


wKioL1ajKIKRhN0_AAAE1K14aVk134.png


若是咱们有一个LIST,它记录了块的ID,以及块在文件系统中的偏移量,那么咱们定位一个块就很简单了。只须要先找到它在文件系统中的偏移量,而后读取固定大小的数据,就至关于读取了该块的内容了。这个LIST,如同不少数据库的元数据信息同样,即数据字典。至于,读取块的内容,固然就是磁盘IO了,这也是数据库提高性能的一大障碍。要知道一块磁盘只有一个磁头,全部的请求都串行化,自己读取的速度并不慢,慢就慢在须要磁头定位以及寻道。所以,咱们但愿加大内存,加大内存命中率,减小磁盘IO,可是毕竟内存是有限的,必然存在一些数据被加载内存,一些数据从内存中OUT,从新写回磁盘。数据库的这种对内存的管理策略,可能相似LRU最近最少使用,或者其余更加“智能”点的策略。通常数据量较小,而且并不频繁更新的信息,好比数据字典这类数据就能够常驻内存中。


块的层次


wKiom1ajLaawmSa6AABC6JCTsWs620.png

咱们知道每一个学生都有一个学号,可是仅仅有一个学号是不够的,一般学生都是XXX年级YYY班级的,对学生进行一个层次的抽象,这样将方便学生的管理!而数据库中块也是同样的,若是将多个块组成一个group,多个group组成一个segment,多个segment组成......当块造成了层次后,天然方便了更高层次的数据管理,好比一个表的多个分区的管理。


表的修改以及删除


若是一个块的大小是16K,一行数据100Byte,也就是块内至多能够储存160行数据。

假设咱们要对表结构进行修改,增长一个字段,而且修改后,当即填充此字段的值,那么会发生什么?


若是块内数据存储是满的,那么天然要在新块中进行存储,也就是说一行数据被分在了不一样块中进行存储,那么原本读取一个块就能够知道一行数据的,如今变成了2个块,也便是增长了IO的次数。也就是在这种状况下,出现了这样的状况:修改表的结构居然使得读取变慢了!


若是块内数据存储不是满的,即自己块内是预留了一部分空间,那么可能增长一个较小的字段,还能存放下,增长较大的字段,那么仍是会出现上面假设的状况。


假设咱们要删除一个表的数据呢?

表的数据存在哪里呢?块里面有,内存里面也有吧!

若逐行删除数据,会很慢,为什么?


既然要按照行去删除数据,那么必需要先找到数据,那么天然涉及到块的定位,行的定位等;而若是基于块的体系结构去删除,那么确定会快不少;若是数据对应的就是文件的话,那么直接删除文件好了。

固然有些数据库的删除表数据,是一种“假删除”,会更加快,并且删错了还能够“吃后悔药”。这种“假删除”,只须要将原表的元数据的状态置为不可用,让外界访问不到它,若是想恢复,那么很简单,只须要重置它的状态为可用就能够了。


那么内存中的数据如何处理呢?

为了使得每次访问数据,不至于都发生磁盘IO,所以内存中会有一部分数据存在,若是将数据库的内存配置很大,好比48G,会装入不少数据,这种状况下若是进行内存区域的所有扫描,也会很花费时间,那么如何快速定位一个表在内存中的数据呢?


wKioL1ajQwvAvzIGAAA4FmTTAhg488.png



预编译SQL


做为Java开发,咱们常常说对数据库的SQL应该要采起PreparedStatement这种预编译的方式,不要采用字符串拼接的方式,由于能够防止SQL注入以及更加快。那么为何预编译SQL会快呢?


数据库拿到SQL后,应该要对其进行解析操做,要将SQL转变成操做,是读,是写,什么表,表与表什么关系,什么字段,什么条件等等。


若是每来一个SQL,都进行解析操做,那么在高并发的系统当中,是否能够优化呢?对于像Oracle这样的数据库,对于SQL有“软解析”和“硬解析”。若是将有些SQL的解析结果缓存起来,当下次还有这样的SQL请求时,就能够不用作解析操做,直接拿取缓存中的解析结果就能够了,这样的SQL解析便是“软解析”。而对于采用字符串拼接的SQL,数据库会认为每次SQL都不一样,所以都会进行解析操做,这就是“硬解析”。要知道SQL解析结果的缓存也是存放在内存中,所以也有大小限制,对于硬SQL,会频繁对SQL解析缓存IN/OUT,致使本该留在SQL解析缓存的SQL被替换,而软解析很好的避免了这种状况。


为什么要小表驱动大表?


之前常常遇到这样的状况:有2张表T1,T2,T1数据量较小(好比几十条),T2数据量很大(好比千万级别)。咱们说应该让T1做为驱动表,这样会快不少。


为何会快不少呢?谁做为驱动表有关系吗?M*N和N*M大小不是同样的吗?这根本上说不通啊。


其实是这样的,咱们但愿小表驱动大表,是想外层循环用小表,这样外层循环的次数不多,内层循环用大表,但愿大表的查询去走索引,即便大表很大,走索引的话,也会很快。所以这才是小表驱动大表快的根本缘由。反之,若是大表做为外层循环,那么即便小表走索引,因为外层循环次数太大,也会很慢的。



索引的基本原理


索引就像是一本书的目录同样,能让咱们快速的找到具体的章节,可是若是增长了书的内容后,也得更新目录。目录一般应该是有序的,目录让查找变快,也让增长变慢。


那么为何索引有这样的做用呢?


索引,是一种结构,好比主流的B+树索引,非主流的HASH索引;索引是一种数据,也须要储存起来。下面咱们就来简单分析下B+树索引的基本原理。


索引,也是储存在Block上,好比对于A表的F字段上创建了索引的话,那么索引一般会存储该字段的值,以及对应的数据位置,以便和数据内容联系起来。要知道,索引存储的宽度要比数据存储区域的宽度小的多,所以一个索引Block每每会存储上万级别的数据,比数据Block要多不少。

wKiom1akQ7aQtAR5AAAWyv6Vxjo789.png

若是select F from A where F = 1,那么会怎么样呢?


要知道F上创建了索引,若是咱们直接走索引查找的话,那么遍历索引存储区域,只须要读数量很小的Block就能够获得了,并且在索引上就有F值,所以查找到就能够直接返回了。而若是走数据存储区域查找的话,那么要遍历数量巨大的Block。


咱们再来看一条SQL:select F , P from A where F = 1,又会怎么样呢?(P只是普通字段)

固然咱们能够先走索引肯定数据范围,可是因为索引上并无P值,此时就得“回表”查找,也就是利用索引查找到的位置,到源表中取得数据。


此时此刻,咱们应该对索引有所体会了,可是好像没有什么树的概念?


随着数据量的增大,固然索引也会变大,会变成多层次的结构,如同数据块同样!


wKioL1akSKjjFH9MAAA97M_DuMY207.png


到这里,好像有点树的形象了,实际上出现了索引管理块的概念,也许随着索引数据的增多,会出现多层次的索引管理块。真正存放索引数据的块,称为叶子块,叶子块存有双向指针,主要是为了快速遍历而设计的。好比:

select count(F) from A

F在A表上是创建了索引的,那么找出F的数量,很简单,就是遍历叶子块大小而已,而经过双向指针又加速了遍历,因此很快咯。


结束语

之前,咱们所相信的,认为的那些事情,是有缘由的,有场景的,我想只有懂一点数据库基本原理,才能更好的运用到工做上。

相关文章
相关标签/搜索