一 平常经常使用外键和外键和级联吗,可以说说你对它们的理解吗?

对于外键和级联,阿里巴巴开发手册这样说到:

【强制】不得使用外键与级联,一切外键概念必须在应用层解决。

说明:以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风 险;外键影响数据库的插入速度

为什么不要用外键呢?大部分人可能会这样回答:

  • 增加了复杂性:  a.每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦,测试数据极为不方便;b.外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
  • 增加了额外工作: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源; (个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。 所以,我觉得这个影响可以忽略不计。 )
  • 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
  • 对分不分表不友好 : 因为分库分表下外键是无法生效的。
  • ......
我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。 实际上,我们知道外键也是有很多好处的,比如:
  1. 保证了数据库数据的一致性和完整性;
  2. 级联操作方便,减轻了程序代码量;
  3. ......
所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分不分表,并发量不是很高的情况还是可以考虑使用外键的。
我个人是不太喜欢外键约束,比较喜欢在应用层去进行相关操作。
二 解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?
池话设计应该不是一个新名词。 我们常见的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。 这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。 就好比你去食堂打饭,打饭的大妈会先把饭盛好几份放那里,你来了就直接拿着饭盒加菜即可,不用再临时又盛饭又打菜,效率就高了。 除了初始化资源,池化设计还包括如下这些特征: 池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。
数据库连接本质就是一个 socket 的连接。 数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存。 我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。 为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。 在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。 连接池还减少了用户必须等待建立与数据库的连接的时间。
三 分库分表之后,id 主键如何处理?
因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要一个全局唯一的 id 来支持。
生成全局 id 有下面这几种方式:
  • UUID: 不适合作为主键,因为太长了,并且无序不可读,查询效率低。 比较适合用于生成唯一的名字的标示比如文件的名字。
  • 数据库自增 id : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。 这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。
  • 利用 redis 生成 id : 性能比较好,灵活方便,不依赖于数据库。 但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。
  • Twitter的snowflake算法 : Github 地址: https://github.com/twitter-archive/snowflake。
  • 美团的Leaf分布式ID生成系统 : Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。 感觉还不错。 美团技术团队的一篇文章: https://tech.meituan.com/2017/04/21/mt-leaf.html 。
  • ......
四 用过 BigDecimal 吗?为啥要用它?
4.1 BigDecimal 的用处
《阿里巴巴Java开发手册》中提到: 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
 
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false
具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(精度丢失),我们如何解决这个问题呢? 一种很常用的方法是: 使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。
 
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);// 0.1
BigDecimal y = b.subtract(c);// 0.1
System.out.println(x.equals(y));// true
4.2 BigDecimal 的大小比较
 
a.compareTo(b) : 返回 -1 表示小于,0 表示 等于, 1表示 大于。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
4.3 BigDecimal 保留几位小数
通过 setScale方法设置保留几位小数以及保留规则。 保留规则有挺多种,不需要记,IDEA会提示。
 
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255
4.4 BigDecimal 的使用注意事项
注意: 我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 BigDecimal(String)构造方法来创建对象。 《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。

大厂面试官最喜欢问的面试难点_级联

 

《阿里巴巴Java开发手册》对这部分BigDecimal的描述
4.5 总结
BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
五 Java 中 IO 流分为几种?
  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式分类结构图:

大厂面试官最喜欢问的面试难点_浮点数_02

IO-操作方式分类
按操作对象分类结构图:

大厂面试官最喜欢问的面试难点_浮点数_03

IO-操作对象分类
六 既然有了字节流,为什么还要有字符流?
问题本质想问: 不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答: 字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。 所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。 如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
tips:大家可以关注微信公众号:Java后端,获取更多优秀博文推送。

 

-END-