三高架构之数据库

三高架构之数据库

架构师:下面传达,上层汇报,系统评估。
收益:业务处理(Java代码),内存读写|数据库操作,涉及磁盘IO。
阿姆达尔定理(t新=t老时间*((1-p)+p/r)。

增强比例高的,p高的,加速比大的。

数据库IO操作,是个可以优化的点
单点压力:多个web服务,挂一个库,单点系统。
时间积累:业务系统,代码系统,数据量在增加,检索和读写会慢慢有压力。

数据库设计

项目个人职责

数据库选型

是否海量数据 大数据?
数据结构。行 or 列。
是否宽表:字段数。
数据属性:业务数据常用的?辅助数据(日志)
要求事务
实时性:写要求高,读延迟低。
查询量大量记录的少数列,还是少数记录的所有列
一致性要求
对增删改查的要求:检索少量数据,单条数据增删改查,批量数据导入等等

如何区分使用关系型数据库or非关系数据库
所有系统的99%业务,都用结构化业务

关系型:mysql,oracle,sqlite
列式:HBase
键值对:redis,memcached
文档:mongoDB
时序:influxDB
搜索:ES

sql和nosql 是一个互补的关系,根据不同场景选择不同的数据库

不知道这个表里有多少个字段?mongoDB文档数据库

存储引擎选择

支持的字段
数据类型
锁类型
索引
事务

innoDB 支持hash索引?不支持,如果非要创建?不报错,会被替换成b+tree

表结构设计

先设计关系–>对象?
好处:先考虑全,来什么需求都接得住。扩展性高
项目工期不急,稳定性要求高,拓展性要求高。
拿着需求–>数据库

先设计对象–>关系?
大部分人,好处:便于理解和实施。
缺点:来个需求,数据库变动会比较大。

重点考虑 底层数据关系,辅助:对象关系。

目的:每个字段好好斟酌,是否会引起下面的问题?
数据不一致:两个表,都有这个值,改了一个表,还有个表没改
数据冗余:反范式。
crud异常:某些数据的缺失,导致数据无法插入

规范化的东西:范式。反范式。

设计时,脑子里:悬挂范式,用于指导设计。
先遵循范式,然后根据业务,再做反范式。

数据库范式

6个,只用3个。逐级递增,越来越严格。

属性:表字段
记录:表行
超键:超码,在关系中能唯一标识 记录的属性集。

一个小范围内所有的人,没有重名的,身份证,姓名,性别,年龄
超键:身份证。姓名。姓名和性别。姓名和性别和年龄。
超键不是最小唯一的。

候选键:候选码,不含有多余属性的超键。
身份证。姓名。

主键:主码,从候选键中,人选一个,叫主键。根据场景,来定主键。

生产中:用户信息中,有身份证。但是我们不用它当主键。

主属性:某一个属性,是候选键,它为主属性。
如果选身份证做主键,姓名就是主属性。根据业务来。

外键:其他表的主键

面试题1

在这里插入图片描述
问:不能作为候选键的属性集合:
A 学号
B 学号,姓名
C 年龄,系别
D 姓名,性别
E 姓名,专业

超键>候选键>主键

超键 ABCDE
候选键 B

设计

版本一
学生表:学号,姓名,班级号,学生在班级号中的序号,性别,班主任,年龄,是否成年,身份证号。

假设:学生会重名。
学而生在班级号中的序号,是由 姓名首字母、插入时间排序

插入学生,序号定不下来,插完后还要排序才能确定序号,影响性能。

标杆。考虑问题的时候,才会有条理,不至于是头脑风暴。
比如说点菜,领导点个鱼看下价位,根据这个价位去选其他菜

数据库的标杆:范式

范式
第一范式

第一范式:原子性。列不可再分割。

学生表:学号,姓名,班级号,学生在班级号中的序号,性别,班主任,年龄,是否成年,身份证号。

看业务。
比如需要展示 姓,A同学,B同学,王同学,欧阳同学,在这种场景下第一反范式不满足,应把姓和名分开
从业务出发,技术是为业务服务的

版本二
学生表:学号,姓,名,班级号,学生在班级号中的序号,性别,班主任,年龄,是否成年,身份证号。

如果不满足第一范式,会导致数据表的不可用。就得在代码层面处理,把非原子化的属性,拆成原子性,再使用。拆分可能会非常复杂,甚至无法实现。

需要有前瞻性的拆分属性,看经验,以及对产品需求的理解程度。

第二范式

非主属性,必须完全依赖于 候选键
完全依赖
元组:记录。关系:表。
函数依赖:x、y =一堆属性的集合。
部分函数依赖:存在x决定y,x中有x·能推出y。 y部分依赖于x
在这里插入图片描述
全完依赖
X推出y,y依赖x
x·属于x,如果x·推不出y,说明y完全依赖于x
一句话:确保表中的每列都和主键相关。

班级号,学生在班级号中的序号(候选键),
班主任能由班级号决定,没有完全依赖于: 班级号,学生在班级号中的序号
只是部分依赖于学生在班级号中的序号。不满足第二范式

如果不满足第二范式,会有数据冗余和数据不一致的问题。班级的班主任在班级的每个同学中都存了一份,如果班主任发生变成,需要修改每个学生的班主任信息,修改的东西有点多,而且可能有理由,会有数据不一致,而且影响性能。

版本三
学生表:学号,姓,名,班级号,学生在班级号中的序号,性别,年龄,是否成年,身份证号。
班级表 班级号,班主任

第三范式

非主属性,不依赖于其他非主属性。
确保每列都和主键相关,而不是间接相关(消除传递依赖)。

年龄,是否成年。
学号可以推出年龄,再根据年龄推出是否成年,这是间接依赖,不满足第三范式。
消除 非主属性对候选键的传递依赖

版本四
学生表:学号,姓,名,班级号,学生在班级号中的序号,性别,年龄、身份证号。
班级表 班级号,班主任
年龄关系表:年龄,是否成年

类似的业务场景,在生产环境中,我们往往会用:代码里用字典,函数计算(这个函数的输入值是年龄,输出值是 是否成年)。
性能上:维护 年龄关系表,是有消耗的。维护代码中的判断,也是有消耗的。使用函数,消耗少了,性能自然会上去。

插入一个年龄 1次,函数计算 修改1次。 2次,查询的时候,查就好,无计算。
插入 年龄1次,函数 无。查询的时候,每查一次(用户的请求量),代码计算一次,用户请求越多,程序付出的代价越大。

BCNF范式

巴斯范式

每个属性,都不依赖于主属性。

版本四
学生表:学号,姓,名,班级号,学生在班级号中的序号,性别,年龄、身份证号。
班级表 班级号,班主任
年龄关系表:年龄,是否成年

假设:学生在班级号中的序号,是由姓名首字母、插入时间排序来的。

50人。学生在班级号中的序号 不能为空。

性能:要插完才能知道最终结果(学生在班级号中的序号),insert还要update,没必要,设计上的不合理,导致操作上的性能低。
为了避免上面的情况。

版本五
学生表:学号,姓,名,班级号,性别,年龄、身份证号。
班级表 班级号,班主任
年龄关系表:年龄,是否成年
班级序号表:学号,学生在班级中的序号

先插入学生表,再插入班级序号表。
写延迟,降低了平均响应时间。

主键是慢慢明确的。

第四范式

禁止主键和非主机有一对多关系。

版本五
学生表:学号,姓,名,班级号,性别,年龄、身份证号,联系电话
班级表 班级号,班主任
年龄关系表:年龄,是否成年
班级序号表:学号,学生在班级中的序号

一个学生有多个联系电话。
加一个号码簿:学生,电话。

生产中:逗号隔开 字符串 分隔符,json。

第五范式

要求:每个依赖关系,都有候选键推出。
不能被无损的分解成 几个更小的不同候选键的不同表

权限设计。
人 角色 功能
张三 超级管理员 修改学生
张三 超级管理员 修改老师
张三 超级管理员 修改班级
李四 管理员 修改学生

加个王五超级管理员,需要加三条记录。

人 角色
角色 功能
这样只需要人加个角色就好。

反范式

关联查询,需连表查询。io次数增多,查询数据慢。
做适当的冗余。
生产中:订单表,中存储商品名称。如果完全按照范式:纠纷,需要快照。
保留当时交易的历史信息。
从业务角度考虑。

学生表,xx,xx,班主任姓名。增大了存储空间,增大了更新风险。
事务保证,效率变低。不用事务,数据可能会不一致。

优化

索引,历史数据的拆分

第一方法:索引。绝佳手段,能缩小一个数量级。
原理:index,目录,字典。

比如hash索引

7,8,9,10,11
hash%3
1,2,0,1,2
先取模,然后去对应位置找,哈希膨胀。

适合:等值查询,不适合区间查询。hash无序
性别,删除表示,值差异很少的列,1。0 膨胀概率非常高。

利弊:
存储空间增大,更新效率降低。

本质:用空间换时间。
多读,少写

性能和一致性

数据库是并发操作的高地。

写细分:
单条:锁一行记录,并发的
表:排序 出号的问题。

乐观锁(针对操作方来做优化,不考虑数据库,利它策略)
1.读取 被操作对象,记录某个特种信息。
2.拿着对象,进行业务逻辑处理,得到新的对象。
3.向数据库写会,新的对象。
4.校验 特征信息。
5.如果一致,写。如果不一致,退或者重试(再重新读,再写一遍)。

特征信息策略:
版本号:增加一个属性,值:时间戳,自增id。
如果属性的值比较丰富的话,可以用被更新的属性。 1改成2 where xxx=1.

已经上线了,1个表10个字段,原来没有考虑锁,发现出现了 些冲突。
最快的解决方法:全部属性当特征。
1.所有字段做成hash
2.执行业务逻辑
3.校验hash

读写策略,没有在被操作对象上加任何限制。
机制:重复读写(牺牲了操作方的性能,而提升被操作方并发性能的策略)

极少修改。减少操作方重复尝试的概率。

悲观锁(利己策略*

操作方对被操作对象 加锁,等释放锁之后,其他操作方才能加锁,做该做的事。

S锁:Shared Lock。共享锁,读锁。
加了s锁,可以加s锁,不可以加x锁。
select * from xx lock in share mode.

X锁:Exclusive Lock。排他锁,写锁。
加了x锁后,其他对象,拿不到任何锁
select * from xx for update

默认的select语句有锁吗?
实验:
先设置隔离级别,开启事务。
加锁:s锁,x锁
执行一些测试的sql,可查,可改。
关闭事务。

同时。开另一个窗口,加s锁,x锁,来做测试。
结论:
加了s锁,可以加s锁,不可以加x锁。

死锁
一个请求的时候,无所谓。
当同时 高并发请求来时,会发生多个 操作方因为竞争资源而相互等待,造成了没有外界参与
无法打破的僵局。

A,B上s锁,后修改想上x锁,结果不行(先读后写)

u锁、;更新锁。
加了u锁,我可以操作,但是其他对象,不能加u,x只能加s(mysql不支持)

原则:加对共享性影响最小的锁,能加s锁,不加u锁,能加u锁,不加x锁
从最大限度保证 被操作对象的并发性能。最小知道原则。

例子:房间,2把锁,同时有两个钥匙,才能打开。
A和B,每个人拿了一把锁,并且绝不给别人。

乐观锁会死锁吗?不会,因为不占用资源。
四个条件:
分析产生某个事情的条件,然后根据条件,逐一解决,问题就解决了
1.互斥条件:钥匙,一个人拿了,另一个人不能拿
2.不剥夺条件:拿了锁的人,没人能让他放手
3.请求和保持条件:有个一个资源,再请求另外一个资源。
4.循环等待条件:环状。

解决:打破其中一个即可。
打破互斥条件:经常发生在 写操作的时候,加了x锁,不能加其他锁,所以资源很难共享。乐观锁 变相处理。
打破不剥夺条件:要求,操作方在已获得资源的条件下,如果要申请别的资源,如果申请失败,放手。主动让出资源。
打破请求和保持条件:保持,请求只留一个。只请求不保持:只能持有一个资源,要请求其他资源,放手。
只保持不请求:需要的资源一下子都获取到,别去找其他资源了。
打破循环等待条件:排好序,葫芦娃救爷爷。

一般打破,后三个条件。

悲观锁分类:
行锁 where id=1;
页锁 锁定相邻的记录。 where id bewteen 1 and 10
表锁 update set xx=1 不写where

目的:写代码防止死锁,破防之后,在死锁发生时,尽快定位并解决。
程序死锁如何定位?
看程序静止的点。分析线程dump文件定位死锁。
数据库死锁如何定位?
查看死锁日志。

解决:根据上面4个条件,找一个破掉。

事务

一组操作的组合,这组操作要嘛全部成功,要嘛全部失败,不存在中间状态

ACID
A 一致性:WAL(Write Ahead Log)预写日志
修改并不直接写入到数据库文件中,而是写入到另外一个称为 WAL 的文件中;如果事务失败,WAL 中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。
优点
读和写可以完全地并发执行,不会互相阻塞(但是写之间仍然不能并发)。
WAL 在大多数情况下,拥有更好的性能(因为无需每次写入时都要写两个文件)。
磁盘 I/O 行为更容易被预测。
使用更少的 fsync()操作,减少系统脆弱的问题。

mysql 通过 redo、undo 日志实现 WAL。redo log 称为重做日志,每当有操作时,在数据变更之前将操作写入 redo log,这样当发生掉电之类的情况时系统可以在重启后继续操作。undo log 称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之间的状态。mysql 中用 redo log 来在系统 Crash 重启之类的情况时修复数据(事务的持久性),而 undo log 来保证事务的原子性

C 一致性:外键,唯一索引等

I 隔离性:锁,mvcc

D redo log:逻辑、物理方面保证。一共写2行,只写了一行,宕机了。日志找。

隔离和系统性能的优化

多个事务之间不干扰,串行执行,但性能不高。
越低级别的隔离,支持更高级别的并发,并且系统开销更低。

假设:没有隔离。
1.
脏写,更新丢失。
2.
脏读:一个事务读取到另一个事务未提交的数据
不可重复读:一个事务多次读取同一个数据,得到了不同的结果。
3.幻读
插入和删除后,发现数据已经有或者没删掉。魔幻。

不可重复读和幻读,操作的数据范围不同。一个操作行,一个操作表。

隔离级别是必要的
事务操作前,给受影响的数据加锁,等事务完成后,释放锁。隔离级别就实现了。

比如直接给被操作对象 加了X锁,实现了串行化,性能急剧下降。
为了性能,允许有一些并发问题。

隔离级别:不是解决隔离性和事务并发之间的矛盾,只是在两者之间,取得平衡
生产中两种方式:细->粗。粗->细

隔离性和并发性,此消彼长。
本质:就是让串行

隔离级别

查询隔离级别,设置隔离级别。
show variables like ‘tx_isolation’ ;
set tx_isolation=""

mysql 可重复读 Repeatable read

I 隔离性:锁+mvcc

S锁,X锁,表锁,行锁。
表锁:开销小,加锁快。不会出现死锁,并发低
系统升级,数据迁移。

行锁:相反。

读未提交(一级封锁协议): 读:查询时,不加锁。写:写的瞬间,加S锁,事务结束释放。

读已提交:读:s锁,读完后,立即释放。(加了s锁,导致别人无法修改)。写:X锁,事务结束才释放。(避免了脏读)
在这里插入图片描述
可重复读:读:s锁,事务完成释放锁(期间别人不能写)。写:X锁,事务完成释放锁

前面都是行锁。

**串行化 (四级封锁协议)**读:整个表s锁,写:整个表X锁

自建事务

原则:第一个成功,第二个出错,让第一个回滚
有些无法建成事务。

方法中(){
发请求:
发邮件:
}

冲正(补偿),撤回

先操作本地数据,再调用方法。

先执行不会对外界造成影响的操作(可以回滚的操作),后执行 不可控的,或者不能回滚的操作。
不可回滚的操作只能有一个。

数据库量大后的优化

随着数据量的增多,sql语句效率下降。

加索引,表区分,分库分表,读写分离。
按照这个顺序优化。

数据文件。分流。

分区优点:
1.不影响业务。
2.放不同磁盘,将读写分散,提升吞吐量
3.备份,恢复,逐步进行。

Range,List,Hash,key.
注意点:
1.结合查询规则,尽量保证查询只会落到一个分区中。减少跨区合并的开销。
2.查询时,带上分区。下标寻址,避免全分区找。

基于我们并行,并发的思想,对读写操作进行分流。
看数据量和业务如何发展。

分区缺点:
一般最多分1024个。
跨区查询

分库分表

1.将表整体分库。不常用
在这里插入图片描述

2.将每个表,分到多个库中。因为数据表量少了,便于检索。同时也能使得行锁和表锁的范围变小。

表拆分:
水平拆分:拆分后的多个表,数据结构一样。
按照规则分配到不同表中

垂直拆分:拆出来的表,只包含原表中的部分属性。
路由操作:需要业务逻辑,将原本指向一个表的读写请求,分散到一个或多个表中,该路由操作和分库分表逻辑一样。
拼接操作:需要业务逻辑,将查询出来的数据进行横向或者纵向拼接,以得到完整的数据

mycat,shardingsphere

场景:经常获取用户的全部信息–水平。
部分信息–垂直
原则:避免跨表操作。

读写分离

还可以用于数据备份
x锁,阻止了其他操作的 读和写。
s锁,可以并发读。

将读写分开,保证读不受写的约束,还可以将多个写并发执行。
适用于:读多写少。
读少写多,提升效果并不明显。

路由:
select —读库上
add update delete create —写库上

主从复制:确保主从数据的一致性。

主:写。从:读。
将主库的数据同步到从库,避免:对从库的写操作,不让从库上出现x锁。

binlog
1.主库开启日志记录功能。将写操作记录在binlog中
2.binlog被发送到从库中,写入从库的RelayLog
3.从库解析RelayLog,重现数据。

STATEMENT:操作语句。可读性好。如果一些涉及到当前时间的函数 now()数据不一致了。
ROW:记录。执行效率高,避免时间函数导致数据不一致。
MIXED:操作和记录的混合。主要写入操作语句,必须的时候,记录(大部分情况使用)

主从复制的延迟问题:
日志:记录,传输,解析
异步复制:降低写的响应时间,可能导致数据不一致或丢失。
半同步复制:写请求,写入主库后,日志至少被送到一个从库上,写才返回。(谷歌开发了mysql半同步的插件)
全同步复制:全写完,才响应用户。

平时如何用:
1.写入主库。
2.在存储()里留一个记录,唯一标识。
3.当读发生的时候,中间件要判断记录是否同步到了从库中。

采用:时间阈值。redis:记录 ttl(10s)简单粗暴

存储的特殊需要

非关系型数据库。

nosql:

内存数据库
列存储数据库:行存储:写 具有优势。clickhouse.
面向对象数据库:DB4o,ObjectDB,ObjectivStore
文档数据库:mongoDB
图数据库:Neo4J 社交网络中 人与人的关系

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值