数据库浅析
数据库架构原理
数据库架构
一、连接器
连接器为每个连接请求分配一块内存空间用于会话上下文管理
缺点:
数据库建立连接的过程,花费较多的计算机资源,比较重
解决思路:
应用启动时初始化一定数量的连接放到连接池中,外部请求 SQL 操作不需要重新建立连接,而是直接向连接池获取连接
通过 TCP 建立连接,连接数据库需要耗费资源,花费一定的时间,通常需要几百毫秒,为了提高效率,故而初始化时,建立连接池,应用程序通过与连接池通信,取得数据库连接
二、语法分析器
语法分析器,针对 SQL,校验 SQL 关键字是否正确以及 SQL 语句是否是正确的书写顺序,进而建立语法树,如果 SQL 错误,无法建立语法树,故而会报出错误
书写顺序
select distinct 字段
from 表 1
[连接类型] join 表 2
on 关联条件
where 限定条件
group by 分组
having 第二级分组
order by 排序字段
limit 起始位置条目索引(偏移量),条目数
实例:
select s_grade from staff where s_city not in (select p_city from proj where s_empname=p_pname)
三、语义分析与优化器
针对复杂嵌套的 SQL 进行等价转化,得到几种关系代数计算结构,利用索引等信息进行优化
关系代数有选择、投影、交、积、连接等有限的结构
数据库中最有技术含量的地方就在语义分析与优化器,毕竟 SQL 会写得非常复杂
select f.id from orders f where f.user_id = (select id from users);
select f.id from orders f join users u on f.user_id = u.id;
四、执行引擎
使用 explain 查看执行计划
执行顺序
from 表 1
on 关联条件
[连接类型] join 表 2
where 限定条件
group by 分组
having 分组后的筛选
select distinct 字段
order by 排序字段
limit 起始位置条目索引,条目数
PrepareStatement
预先提交带占位符的 SQL 到数据库进行预处理,提前生成执行计划,当给定占位符参数,执行引擎才真正执行 SQL
PrepareStatement 可以防止 SQL 注入,因为语法结构已经固定,不会因为输入参数的不同导致语法结构改变
PreparedStatement updateUser = con.prepareStatement("UPDATE Users SET
stateus = ? WHERE userID = 233");
updateUser.setInt(1, 2);
updateUser.executeUpdate();
扩展:
防止 SQL 注入的手段,有两种手段,第一种,SQL 预处理的方式,第二种,增加中间件,防止用户请求落到数据库层面
架构师思维
知道该怎么做,还必须知道为什么要这么做,这么做和不这么做的后果是什么
数据库事务
事务特性 ACID
原子性(Atomicity):事务要么全部完成,要么全部取消
隔离性(Isolation):两个事务 T1 和 T2 同时运行,事务 T1 和事务 T2 最终的结果是相同的,不管 T1 和 T2 谁先结束,隔离性依靠锁实现
持久性(Durability):事务提交,数据持久保存到数据库中
一致性(Consistency):只有合法的数据(依照关系约束和函数结束),才能写入数据库
事务日志
(1)事务操作时,事务日志文件记录更新前的数据记录
(2)更新数据库记录
(3)如果全部更新成功,则事务提交
(4)如果某条记录更新失败,进行事务回滚,数据恢复到事务提交之前的状态,保证数据一致性
锁
目的
保证数据的一致性,当多个线程并发访问同一个数据时,只允许一个线程进行访问处理,进而保证数据的完整性和一致性
数据库中事务的隔离级别,通过锁实现
分类
(1)按照锁定对象的粒度划分
行锁
按照行对数据进行锁定
优点:锁定粒度小,发生锁冲突概率低,实现的并发度高
缺点:锁开销比较大,加锁比较慢,容易死锁
页锁
按照页对数据进行锁定
锁定粒度、并发度、开销,处于行锁和表锁之间
表锁
对数据表进行锁定
优点:锁的开销小,加锁比较快
缺点:锁定粒度大,发生锁冲突概率高,实现的并发度低
注意:
锁会占用内存空间,锁空间的大小是有限的,当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级,锁升级就是用更大粒度的锁替代多个更小粒度的锁
(2)按照数据库管理的角度划分
共享锁
共享锁也叫读锁或 S 锁,共享锁锁定的资源可以被其他用户读取,但不能修改,而自身事务是可以修改数据的
锁定指令
LOCK TABLE product_comment READ;
解锁指令
UNLOCK TABLE;
行数据加上共享锁
SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id = 912178 LOCK IN SHARE MODE;
排他锁
排它锁也叫独占锁、写锁或 X 锁。排它锁锁定的数据只允许进行锁定操作的事务使用,其他事务无法对已锁定的数据进行查询或修改
数据表添加排他锁
LOCK TABLE product_comment WRITE;
释放排他锁
UNLOCK TABLE;
行数据加上排他锁
SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id = 912178 FOR UPDATE;
当程序对数据进行更新操作时,即 INSERT、DELETE 或者 UPDATE,数据库自动使用排它锁,防止其他事务对该数据行进行操作
意向锁(Intent Lock)
给更大一级别的空间示意里面是否已经上过锁
意向共享锁、意向排他锁
(3)从程序员的角度对进行划分
即看待数据并发的不同思维方式
乐观锁(Optimistic Locking)
对同一数据的并发操作不会总是发生,不用每次针对数据采用数据库自身的锁机制,而是通过程序实现
实现方式
版本号机制
在表中设计一个版本字段 version,第一次读的时候,会获取 version 字段的取值。然后对数据进行更新或删除操作时,会执行 UPDATE ... SET version=version+1 WHERE version=version
时间戳机制
在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行比较,如果两者一致则更新成功,否则就是版本冲突
乐观锁就是程序员自己控制数据并发操作的权限,基本是通过给数据行增加一个戳(版本号或者时间戳),从而证明当前拿到的数据是否最新
悲观锁(Pessimistic Locking)
认为同一个数据可能会被其他事务并发修改,故需要通过数据库自身的锁机制实现,保证数据操作的排他性
乐观锁与悲观锁的比较
1、乐观锁适用于读操作较多的场景,锁机制通过程序实现,不存在死锁,阻止不了程序以外的数据库操作
2、悲观锁适合写操作较多的场景,写操作具备排他性,可以在数据库层面阻止其他事务对该数据的操作权限
推荐阅读(关系型数据库)
数据库设计(入门级)
(1)《数据库设计的那些事》(慕课网-在线课程),关键点:数据的范式、SQL 语句用法
(2)MySQL 官方文档
(3)《MySQL 必知必会》
理论基础
(1)《数据库系统概念》(翻译不够好)
核心知识:数据库查询语言、模式设计、数据仓库、数据库应用开发、基于对象的数据库和 XML、数据存储和查询、事务管理、数据挖掘与信息检索以及数据库系统体系结构等方面的内容,对性能评测标准、性能调整、标准化以及空间与地理数据、事务处理监控等高级应用主题进行了广泛讨论
(2)《Oracle Database 9i/10g/11g 编程艺术》
(3)《高性能 MySQL》
(4)《MySQL 技术内幕:InnoDB 存储引擎》,核心:MySql 内部原理
(5)《MySQL Internals Manual》(MySql 官方文档)
(6)《数据库的索引设计与优化》(数据库的索引设计和优化)
主从读写分离
问题
数据库单机部署,4 核 8G 运行 MySql5.7,大概支撑 500TPS 和 10000QPS,一旦进行营销活动,导致数据库查询和事务压力增加
主从读写分离
客观事实
大部分系统的访问模型是读多写少,读写请求量的差距可能达到几个数量级
解决方案
如何抵抗更高的查询请求,需要把读写分开,这样还可以针对读进行伸缩扩展
两个关键技术
(1)数据的拷贝,即主从复制;
(2)在主从分离的情况下,如何屏蔽主从分离带来的访问数据库方式的变化,让开发如同在使用单一数据库一样
主从复制
关键在于 binlog 日志文件,binlog 记录 MySql 所有变化并以二进制形式保存在磁盘上二进制日志文件
过程
优点
(1)写入操作依赖主库,读出操作依赖从库,即使写入操作导致行锁或者表锁,也不会影响从库的读数据操作
(2)读流量较大时,通过部署多个从库共同承担读流量,即“一主多从”,用以抵御大量的并发读操作
(3)从库作为一个备份,避免主库故障导致数据丢失
缺陷
(1)部署的复杂性
(2)容量并没有变化
(3)主从复制的延迟
在互联网的项目中,一般会优先考虑性能而不是数据的强一致性,故可以适当容忍延迟
核心解决思想:尽量不去从库读取数据
第一种方案是数据的冗余
消息队列不仅包含信息 ID,还包含所有的处理信息,避免从库读取数据
第二种方案是使用缓存
进行写操作时,将数据信息存储到缓存中,其他业务处理时,从缓存中获取数据
第三种方案是查询主库
读写混合操作较为紧密时,则查询数据从主库获取,这样保证数据的一致性,实际上,管理平台系统由于用户少,数据操作往往读写同时进行,为了保证有效性,可以采用这种查询主库的方式,尤其是读写操作交叉的业务环节里
如何访问数据库
传统访问方式
单台服务只需要一个地址,一主多从模式,则需要一个主库地址和多个从库地址,并且需要区分写入操作和查询操作
新兴访问方式
为了降低实现的复杂度,业界涌现了很多数据库中间件来解决数据库的访问问题,这些中间件可以分为两类
第一类
以淘宝的 TDDL( Taobao Distributed Data Layer)为代表,以代码形式内嵌运行在应用程序内部,可以看成是数据源的代理,它的配置管理多个数据源,每一个数据源对应一个数据库
第二类
单独部署的代理层方案,如早期阿里巴巴开源的 Cobar,基于 Cobar 开发出来的 Mycat,360 开源的 Atlas,美团开源的基于 Atlas 开发的 DBProxy 等,业务代码如同在使用单一数据库一样使用它
聚簇索引与非聚簇索引
聚簇索引
(1)数据库记录与索引存储在一起
(2)MySql 主键就是聚簇索引,主键 ID 和记录行存储在一个 B+树
(3)由于数据在物理上只会保存一份,包含实际数据的聚簇索引只能有一个
非聚簇索引
(1)非聚簇索引的叶子节点记录存储的不是行记录,而是聚簇索引,即主键
(2)通过非聚簇索引找到聚簇索引主键,再由主键找到数据记录,这个过程叫做回表
添加必要索引优化 SQL 查询性能
没有索引,查询数据需要遍历所有记录,时间复杂度为 O(n)
合理使用索引
1、不要盲目添加索引,尤其生产环境
(1)添加索引 Alter 操作会消耗较长的时间
(2)Alter 操作期间,数据库的增删改查操作全部阻塞
(3)插入一条新纪录,索引越多,维护的 B+树越多,性能越差
(4)合理使用索引,主要针对的是二级索引(即非聚簇索引,因为聚簇索引是必须存在的,也就是必须有主键)
2、删除不用的索引,避免过多消耗空间
(1)使用更小的数据类型创建索引
(2)int 4 字节 bigint 8 字节,Timestamp 4 字节 Datetime 8 字节
评论