软件架构设计-数据库

2021-10-08  本文已影响0人  孤独的死锁

范式与反范式

数据库范式的要求

但在互联网应用中,为了性能或便于开发,违背范式的设计比比皆是,如字段冗余、字段存一个复杂的JSON串、分库分表之后数据多维度冗余存储、宽表等。如果系统是重业务性的系统,对性能、高并发的要求没有那么高,最好保证数据库的设计达到第三范式的要求。

分库分表

为什么要分?
拆分维度的选择
Join查询问题

B+树

B+树逻辑结构
B+树物理结构

对于磁盘,是以”块“为单位进行读写的。InnoDB默认定义的块大小是16KB,通过innodb_page_size参数指定。InnoDB每一次磁盘IO,读取的都是16KB的整数倍的数据。无论是叶子节点,还是非叶子节点,都会装在page里。16KB如果用来装非叶子节点,一个page大概可以装1000个key,意味着B+树有1000个分叉;如果用来装叶子节点,一个Page大概可以装200条记录。基于这种估算,一个三层的B+树可以存储多少数据量呢?
第一层:一个节点是一个Page,里面存放了1000个key,对应1000个分叉
第二次:1000个节点1000page,每个page存放1000个key,对应10001000个分叉
第三层:1000
1000个节点,每个page存放200条记录,即是10001000200=2亿条记录,总容量是16KB10001000,约为16GB。
把第一层和第二层的索引全装入内存里,也就约16MB的内存。三层B+树就可以支撑2亿条记录,并且一次基于主键的等值查询,只需要一次IO.
page与page之间组成双向链表,每一个page头部有两个关键字段;前一个page的编号,后一个page的编号。page里面存储一条条的记录,记录之间用单向链表串联。定位到了page,也就定位到了Page里面的记录。因为Page会一次性读入内存,同一个page里面的记录可以在内存中顺序查找。

三层磁盘B+树
B+树物理存储
非主键索引

在InnoDB中,非主键索引的叶子节点存储的不是记录的指针,而是主键的值。且值可以重复,一个key可能对应多条记录。对于非叶子节点,不仅存储了索引字段的值,同时也存储了对应的主键的最小值。


非主键索引B+树

事务与锁

事务的四个隔离级别
事务并发问题
事务隔离级别
悲观锁和乐观锁

悲观锁,就是认为数据发生并发冲突的概率很大,所以读之前就上锁。利用select xxx for update语句。容易造成死锁以及高并发场景下会造成用户端的大量请求阻塞。
乐观锁,认为数据发生冲突的概率比较小,所以读之前不上锁,等到写回去的时候再判断数据是否被其他事务改了。类似于cas,给表结构里加一列verson字段。在实现层面,就是利用update语句的原子性实现了cas。当且仅当version为期望值时,才更新成功。同时,version也必须加1.version的比较,数据的更新,version的加1,这三件事情是在一条update语句里面完成的,这是整个事情的关键所在。

分布式锁

乐观锁的方案限制时select合update的是同一张表的同一条记录。如果是更加复杂的场景,就需要使用分布式锁来解决了。

死锁检测

两个事务发生死锁 多事务发生死锁

以事务为顶点,以事务请求的锁为边,构建一个有向图,这个图被称为wait-for graph。死锁检测就是发现这种有向图中存在的环。检测到死锁后,数据库可以强制让其中某个事务回滚,释放掉锁,把环断开,死锁就解除了。

事务实现原理之1:Redo Log

Write-Ahead

一个事务要修改多张表的多条记录,多条记录分布在不同的page里面,对应到磁盘的不同位置。如果每个事务都直接写磁盘,一次事务提交就要多次磁盘的随机IO,性能达不到要求。解决方案:现在内存中提交事务,然后写日志(所谓的write-ahead log),然后后台任务把内存中的数据异步刷到磁盘。日志是顺序地在尾部append,从而避免了一个事务发生多次磁盘随机IO的问题。所谓的write-ahead log就是redo log。redo log写入的本身也是异步的。在事务提交后,redo log先写入内存中的redo log buffer中,然后异步地刷到磁盘上的Redo Log。


redo log的异步刷盘
redo log物理结构
上一篇 下一篇

猜你喜欢

热点阅读