数据库的存储原理

数据库帮助我们把数据存储到硬盘上,以其他格式的文件。并且在使用的时候帮我们把数据拿到内存。这个拿取的过程遵循了计算机组成原理中的读取硬盘的原则:局部性原则。局部性原则:程序在使用了一个数据后,紧接着很可能会使用存储该数据地址附近的其他数据,这个叫做空间局部性。程序使用了一个数据之后,紧接着很可能会在不久之后再次使用该数据,这个叫做时间局部性

数据库的读取数据方式严格按照计算机组成原理提到的分页模型。一次从硬盘上读好多好多页到内存,一个页大小4KB。通常读取的都是2的整数倍个页。因为从硬盘上拿到内存十分消耗资源,所以数据库在读取数据时尽可能地要减少IO操作。

数据库的执行模型分为三层,一层Client,一层Server,一层存储引擎。存储引擎负责从硬盘上按照要求读取数据到Server层,之后反馈给Clinet。Server层拥有分词器等。

数据库的存储引擎

存储引擎常用的有三种

  1. InnoDB

MySQL的默认存储引擎,是事务性存储引擎,使用聚簇索引。底层数据结构使用了B+树,对所有类型的索引都有较高的兼容性。实现了四种隔离级别,默认隔离级别是可重复读。在可重复读级别下使用多版本并发控制(MVCC)和Next-Key-Locking解决幻影读。支持行级锁,表级锁,事务回滚和提交,并且支持热备份。是一个十分强大的存储引擎。MySQL除非主动设置或者遇到了它处理不了的特性,否则不会更换存储引擎。适用于insert,update,remove特别频繁的数据表。

  1. MyISam

MySQL支持的另一个存储引擎,使用非聚簇索引,不支持事务,不支持行级锁。底层数据结构使用B+树。一般情况下不是很常用,除非某些数据只进行select操作,那么它相较于InnoDB具有优势。

  1. Memory

一个不怎么常用的存储引擎,底层使用的是Hash表。在多行查询和范围查询的业务中能力极差。

数据库的数据结构

传统的数据库使用以下几种模型:

1.哈希表

拿单条数据的效率逼近O(1),但是在数据量极大的情况下,效率依旧偏低,而且对于常用的多行查询,范围查询业务来讲效率极其低下。并且每次查询都要把表中的数据全部拿到内存,比较浪费空间。

 2.红黑树和AVL树

红黑树和AVL树以及二叉树,也是看上去十分适合数据库的数据结构,但是不管他们的思想有多么强大,可能在大批量数据存储的业务环境中确实适用。但是并不适合数据库的情形。就因为数据库是对于硬盘的读取。红黑树和AVL树的每个节点会被当做一个磁盘块,二叉树的深度在大批量数据情况下会很深,虽然总体查询效率逼近二分查找,但是,正因为读取树节点需要IO,那么像这种高深度的数据结构也就对应着多次IO。那么查询效率就会极大幅度降低。

3.B树

B树是一个比较适合于数据库的数据结构,B树遵循一个原则:每个树节点表示一个磁盘块,块中存储两个范围值,两个数值分别为a1,a2。在a1左侧存有装载小于a1值的磁盘块的地址,a1和a2中间存有在二者范围之内的磁盘块的地址,a2右侧是大于它值的磁盘块地址。通过划分范围的多叉树降低搜寻效率。但是需要维护结点关系,在深度过大时,也容易出现效率慢的情况。而且存在一个致命问题。B树由于机制问题,一个磁盘块并不能一次性存储过多数据,那么在大数据量的情况下就会加大搜索深度。B树的每一个结点相当于是一个磁盘块,搜寻一次也就对应了一次IO,搜索深度提高之后,IO次数会变得十分频繁,效率就会低。

mysql 声明局部 mysql局部性原理_数据库

4.B+树

B+树是在B树的结构上优化的产物。B+树拥有全新的思想。B树对于初始的结点处理方式不同,对于低深度的结点,放弃放置数据,选择了存更多范围地址。B树一般只存两个数字划分三个范围,B+树使用了很多很多数字划分了很多很多范围。最直接的影响就是,树的深度被极大幅度降低了。在大量的数据环境下也不用过深地去探索树,通过范围搜寻能够很快的锁定位置。通过减少了IO次数,极大幅度提升了效率。

mysql 声明局部 mysql局部性原理_数据库_02

为什么选择B+树?相较于其他数据结构有什么优势。

B+树对于树深度的优化是前所未有的。在内存上,大批量数据的处理它可能并不是最优解,但是在探索树深度作为时间浪费的主要因素的数据库中,它就是最优解。降低树深度,减少了最浪费时间的IO次数。在大批量数据的情况下也能通过极短的IO时间拿到想要的数据。

数据库的封锁技术

数据库的隔离级别

1.未提交读

事务A的未提交的操作可以被其他事务可见,该隔离级别所有并发一致性问题都会发生

2.提交读

事务A的操作再未提交前,对其他事务不可见,该隔离级别可以防止脏读

3.可重复读

事务重复读取一个数据的结果是一致的。该隔离级别可以防止脏读和重复读

4.可串行化

强制事务串行执行,串行执行的事务互不干扰,不会产生并发问题。该隔离级别不会产生任何并发问题。

数据库的ACID

原子性(Atomic)

数据库的事务应具有原子性,一个事务的多个操作,要么就全部执行成功,要么就全部失败回滚

一致性(Consistency)

数据库的事务,在读取一个数据时,多次读取的结果应是一致的

隔离性(Isolation)

数据库的事务,在提交前,其他事务对此不可见

持久性(Durability)

数据库的事务一旦提交成功,就应该将新的数据保存在数据库中,即便是系统崩溃,执行的事务结果也不能丢失,要通过重做日志( Redo Log )来进行重做。

读写锁

读锁称为S锁

写锁称为X锁

在一个事务为表加上写锁时,该表不能存在其他X和S锁,并且在X锁撤销前,其他S锁和X锁不能加在这个表上,也就是写事务完成前,不能读也不能新写

在一个事务为表加上读锁时,该表不能存在X锁,并且在锁撤销前,只允许其他S锁申请,X锁不行。允许多重读,不允许写打断。

意向锁

读写锁存在一个问题:

当A事务想要给T表加上X锁时,为了防止某个事务为T表的某行加上了X锁,就需要遍历整个表的所有行来查找是否有其他事务的X锁,这个过程是很浪费时间而且无用,开销还大。意向锁就是为了解决这个问题。

意向锁也分为读意向IX和写意向IS。

A事务在为T表加上X锁前,会看看T表上是否存在一个IX锁。如果有,就表明有其他事务为这个表套上了X锁,就不遍历了,提前就可以知道可否执行。S锁也是同理。同时,IS锁和IX锁是可以并存的,并不是真正意义的上锁,更像是一个标记,二者互不影响。但是IX锁不能同时存在多个。

数据库拥有三级封锁协议

一级封锁协议

规定,事务A在执行写操作的时候,必须加上X锁,并且在事务执行结束后才能释放。

这个协议保证了操作不会出现:丢失修改

二级封锁协议

规定,在一级封锁协议的基础上,事务A在执行读操作的时候必须要加上S锁,并且在读操作结束后立刻释放。

这个协议保证了操作不会出现:脏读

三级封锁协议

规定,在二级封锁协议的基础上,事务A在执行读操作的时候必须加上S锁,并且与二级封锁协议不同的是,必须在事务结束后才释放S锁,释放时间更晚。

这个协议保证了操作不会出现:重复读

两段锁协议

加锁和解锁必须分两个阶段进行。

可串行化调度是指,在并发情况下,使得事务的执行结果与串行的结果一致,串行的事务执行互不干扰,不会出现并发一致性问题。

数据库的并发一致性问题

在高并发的情形下,数据库可能会存在一下几种问题:

1.丢失修改

事务A在对数据t进行修改,将t改为10,此时该事务未结束,事务B又把t改为了20。这就是丢失修改。

2.脏读

在事务A对数据t执行修改的时候,t原本=10,事务A修改t=20之后还未提交,事务B来对t进行读,读到了20,之后,事务A放弃提交,执行了回滚,t又变回了10。这就是脏读

3.重复读

在事务A对数据t执行读的时候,第一次读t=10,此时事务B来了,对数据t执行了修改改成了t=20。之后事务A再次读取t,读到了20。这就是重复读

4.幻影读

又称幻读,事务A在执行Count(name)这个命令时,第一次获取到了结果为20,也就是有20条数据。之后事务B删掉了一行数据。事务A又来执行了一次Count(name),第二次获取到了结果为19。这就是幻读

Next-Key-Locking

Next-Key-Locking是InnoDB用来解决默认隔离级别“可重复读”无法避免的幻读问题的。

他包括了Gap LockingRecord Locking的所有特性。

其中RecordLocking会对本节点索引加一个锁,无法避免本节点外的其他值被修改。

Gap Locking会对本节点的缝隙加一个锁,也就是说,在执行

Select a from t where a between 1 and 10

那么,GapLocking会对(1,10]加锁,锁释放前,都无法插入一条5的数据。

Next-Key-Locking不仅会对本节点加锁,也会对节点缝隙加锁。

同样执行上述sql语句,Next-Key-Locking会对(0,1],(1,10]全部加锁。