多年开发实践中遇到的DB相关的话题研究和整理,不介绍DB的基本概念,也不过于深入DB原理,以满足日常应用、知其然知其所以然为准。 包含十几个子话题,含事务传播性、索引优化、拆分、FailOver等。 本
本文主要是对目前工作中使用到的DB相关知识点的总结,应用开发了解到以下深度基本足以应对日常需求,再深入下去更偏向于DB本身的理论、调优和运维实践。
不在本文重点关注讨论的内容(可能会提到一些):
DB的四大特性,这里简单概括下不具体展开。
这里展开讲比较复杂,实践中很少用到,一般满足1NF即可。
高一级必满足低一级。
读现象是伴生于不同的隔离级别出现的。读现象的场景都是在多个事务并发执行的前提下可能出现的:
不同的隔离级别可以防止读现象。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
未提交读 | 可能发生 | 可能发生 | 可能发生 |
提交读 | - | 可能发生 | 可能发生 |
可重复读 | - | - | 可能发生 |
可序列化 | - | - | - |
注:为什么提交读不能避免不可重复读?假设A事务需要读取两次变量a,第一次读取时a=10,执行过程中a被事务B修改变成了20,那么A第二次读时a与第一次的结果不同。
// 查看当前会话
select @@tx_isolation;
// 查看当前系统
select @@global.tx_isolation;
Mysql 5.7.14-ALISQL版默认是提交读。
在多个含有事务方法的相互调用时,事务如何在这些方法间传播。
spring支持7种事务传播行为:
Spring默认是propagation_requierd。
为了便于理解,将以上几种传播行为分类:
传播性的类型 | 当前不在事务中 | 当前在事务中 | 备注 |
---|---|---|---|
propagation_requierd | 新建一个事务 | 加入到当前事务 | 最常见的选择 |
propagation_supports | 非事务执行 | 加入当前事务 | |
propagation_mandatory | 抛异常 | 加入当前事务 | |
propagation_required_new | 新建事务 | 挂起当前事务 | |
propagation_not_supported | 非事务执行 | 挂起当前事务 | |
propagation_never | 非事务执行 | 抛异常 | |
propagation_nested | 新建事务 | 嵌套事务内执行 |
指当前方法不再受所属的事务控制直到该方法结束。比如A方法起了一个事务,调用B方法时B挂起事务,那么B的所有DB操作都不再受A方法的事务控制,直到B执行结束。
嵌套的事务可以独立于当前事务提交或回滚。
确认SQL在实际执行时的执行情况,如是否走上索引、走了哪个索引、扫描行数、执行顺序(如多个select级联查询)
explain XXX
mysql: MySQL_执行计划详细说明
以Mysql的InnoDB为例:
主键是聚集索引。
唯一索引、普通索引、前缀索引等都是二级索引(辅助索引)。
结合B+树的知识,对于聚集索引,索引数据和存储数据是在一起的,比如id-age这个记录。
对于非聚集索引,只有索引数据,定位具体的记录需要通过索引来找,也即通过索引找到id,再通过id找到id-age这条记录。
查询条件和结果全部在一个索引中,MySql不需要通过二级索引查到主键后再查一遍数据就可以返回查询数据。覆盖索引可以大大提升查询效率,举例
select a, b from table_x where c = XXX order by d;
其中a、b、c、d全部在索引中,那么这就是覆盖索引。
对于做不到覆盖索引的查询,查到主键后还要回到数据表中把数据查询出来,则称为__回表__。
对于联合索引,建立(a, b, c)相当于建立(a), (a,b), (a,b,c)。
在这个索引下,遵循”最左前缀原理“,即先按a排序,再按b排序,最后按c排序。
如果缺失了前一列,如where b = xxx,则走不上索引。
如果某一列不是等值匹配,如where a>10 and b = 1,则只能部分走上索引,b走不上索引。非等值匹配有<、>、!=、IN、LIKE等。
更完整的可以参考mysql组合索引的有序性
select...for update,走上索引(含主键)是行锁,没走上就是表锁。但是如果索引匹配过多,也会变成表锁。
[转载&整理&链接]mysql 通过测试"for update",深入了解行锁、表锁、索引
https://www.cnblogs.com/tiancai/p/9024351.html
Https://www.jianshu.com/p/9bd572b0a0d4
https://www.jianshu.com/p/23524cc57ca4
简单概括一下:
B树的中间节点和叶子节点都有不止一个关键字(key)。B树出现的目的是减少磁盘臂移动的开销从而,尽量减少读写的次数。
B+树与B树的不同在于,B+树的数据都在叶子节点上,中间件节点没有数据。
应用:由于B树最左前缀匹配的特性,如果用左模糊查询(like "%xxx")是走不上索引的。
查询第N页(下标从1开始)数据,每页大小PageSize
// 先获取符合条件的总数
select count(1) from tableA where XXX
// 查询该页
// 偏移量,可选 offset = (pageSize-1) * N
// 行数 rows = pageSize
select row1, ..., rowN from tableA where XXX limit offset, rows
SELECT Table1.Row1, Table1.Row2, Table2.Row1
FROM Table1
INNER JOIN Table2
ON Table1.Row2 = Table2.Row2
ORDER BY Table1.Row1
inner join( = join),都匹配才返回
left join,左表全返回不管右表有没有匹配
right join,右表全返回不管左表有没有匹配
full join,全返回,左表右表无论对方匹配都返回所有行
MyBatis缓存分为两级:一级缓存,SqlSession级别;二级缓存,SqlSessionFactory级别。和通常命名习惯相反,二级缓存的作用范围大于一级缓存,原因是,SqlSession是由SqlSessionFactory创建的。
MyBatis默认开启一级缓存,不开启二级缓存。一级缓存生效于同一个SqlSession,当这个session没有做任何update操作且查询完全相同时,会返回一样的数据。
此时,在并发环境下,很有可能会发生这种情况:在一台服务器A上连续查询两次,两次属于同一个SqlSession;中间另一个服务器B对表做了更新,A看到的第二次查询结果仍然是旧的。
关于缓存的细节,如如何判断“同一次查询”、缓存有效期、SqlSession原理,可以自行查阅。推荐mybatis中文官网,有很多原理的介绍。
在实践中,spring和mybatis整合以后每次查询都会刷新sqlSession,即一级缓存是无效的。
MyBatis缓存系列
单独提一下,二级缓存的readOnly默认为false,同一条数据在内存中每个对象都是独立的,可修改相互不影响。可参考如何理解Mybatis二级缓存配置中的readOnly?
我在工作中绝大多数时间都用mybatis+spring/SpringBoot写持久层,只有一个应用因为使用SpringDataJPA才对hibernate才做了一些了解。
看了一些资料,了解到二者在写法以外,性能的差别主要在于多表查询这个场景,hibernate会比mybatis慢一些,原因是
hibernate为了保证POJO的数据完整性,需要将关联的数据加载,需要额外地查询更多的数据。
MyBatis和Hibernate相比,优势在哪里? - 郑沐兴的回答 - 知乎
此外,JPA如果想运行原生sql,可以使用EntityManager。
按日期定时同步迁移及清理线上数据
查询需要根据日期路由到线上库或历史库
按业务,已处理数据及未处理数据拆分。如已受理未申请单和已完结申请单分开保存。
提供更多、更强、容量更大的硬件资源。
在计算机术语中,故障转移(英语:failover),即当活动的服务或应用意外终止时,快速启用冗余或备用的服务器、系统、硬件或者网络接替它们工作。 故障转移(failover)与交换转移操作基本相同,只是故障转移通常是自动完成的,没有警告提醒手动完成,而交换转移需要手动进行。 ——wiki
FailOver是从应用层面做的,不是单纯DB层面。
单库架构,一旦库挂掉整个服务不可用;
主备架构,切换时有时间延迟;
FailOver从分布上来看仍然是主备架构,但是增加了系统自动切换恢复能力。
和去IOE是一致的,用大量相对廉价的硬件,拆分服务,减少单点,提升整体的可用性。
仅举两个最典型的例子,具体场景需要结合硬件能力和应用架构综合分析。
特点:
方案:
注:可以采取双写、基于读库(上文中所述,利用oracle的data guard、mysql的replication等)、异步消息等保证主备一致。
特点:
方案:
为了解决读大于多于写的场景下数据库瓶颈的一种架构模式。同样需要结合具体业务不能生搬硬套。
主要是一写多读的架构,在主库挂掉的场景下有可能需要考虑使用paxos算法来决定新的主库。
在做读写分离前,可以先考虑缓存是否能解决当前场景的问题。
记录DB操作(不含查询)及其他执行信息的二进制日志。
可以参考下面两篇文章简单了解下。
【原创】研发应该懂的binlog知识(上)
【原创】研发应该懂的binlog知识(下)
想起来就补一些。
对于有默认值的非空列,如果在insert语句中指明了这一列且值为null,插入仍然会报错,此时不会取默认值。让该列取默认值的方式是,不让该列出现在insert语句中。
MySql5.6做的优化之一,可以在like查询中提高性能。利用查询子句中能确定的查询条件,减少一次查询匹配到的索引,从而减少回表查询的数据。
可以自行研究的话题,限于笔者接触范围和篇幅,不展开来写。
开发中遵守一些事先约定好的规范,有助于提升研发效率(无论是个人还是团队内部或团队之间),避免犯一些重复错误,也有助于后续的维护。对于《阿里巴巴JAVA开发手册》中的规范,限于篇幅并没有写明原因,笔者基于自己的开发经验进行一些点评,供参考。
本来是想针对《阿里巴巴JAVA开发手册》MySql规范部分这一部分补一下点评的,但是发现前两天新出的泰山版已经补上很多说明,没必要一一点评,直接下载来看就好:https://files.cnblogs.com/files/wuyuegb2312/《Java开发手册(泰山版)》.pdf.zip
可以看出,前面一部分有很多规范都是和Java OOP相关联的。对于部分条目,是之前没注意到的,单独拉出来点评下。
【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标
准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
官方文档提到,InnoDB下count(*)和count(1)是没有区别的:
InnoDB handles SELECT COUNT() and SELECT COUNT(1) operations in the same way. There is no perfORMance difference.
但考虑到其他实现对count()有优化(如MyISAM,前提是没有WHERE和GROUP BY子句,直接取缓存的总数),再考虑到用其他DB的情况,统一起见一直用count(*)就好了。
更详细的分析可以看 为什么阿里巴巴禁止使用 count(列名)或 count(常量)来替代 count(*)
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
禁止使用外键,在本例中并不是不允许在成绩表中存放student_id字段,只是不设置成为外键即可,更新由应用层来做。
--结束END--
本文标题: 应用开发实践之关系型数据库(以MySql为例)小结
本文链接: https://lsjlt.com/news/5885.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-10-23
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0