java面试-数据库

数据库的三范式是什么?

  • 第一范式(1NF)的要求确保每个列都是原子的,不可再分。换句话说,每个列中的数据不能是多个值或多个数据项的集合

  • 第二范式(2NF)在满足第一范式的基础上,确保每个非主键列完全依赖于整个主键而不是部分主键。

举例:考虑一个存储图书订单的表格,其中包含订单ID、图书ID、图书名称和图书作者。如果我们将订单ID和图书ID作为联合主键,那么图书名称和图书作者只依赖于图书ID,而不是整个联合主键。为了符合第二范式,应该将图书名称和图书作者移到一个单独的表格中,并使用图书ID作为主键。

  • 第三范式(3NF)在满足第二范式的基础上,确保每个非主键列之间不存在传递依赖关系。换句话说,任何非主键列不能依赖于其他非主键列。

日常工作中你是怎么优化 SQL 的(针对我的项目进行回答)?

在该项目中,我负责员工登录和人脸考勤功能的开发和优化。以下是我在SQL语句优化方面所采取的措施:

  • 索引优化:针对频繁查询的字段,我在相关表中创建了合适的索引。例如,在员工表中创建了员工ID的唯一索引,在人脸考勤记录表中创建了时间戳的索引。这样可以加快查询速度并减少不必要的全表扫描。
  • 批量操作优化:为了减少与数据库的交互次数,我采用了批量操作来提高性能。在人脸考勤功能中,我使用了批量插入的方式将考勤记录一次性写入数据库,而不是每次单独插入一条记录,从而减少了数据库操作的开销。
  • 查询语句优化:我对复杂的查询语句进行了优化,避免了不必要的JOIN操作和嵌套子查询。例如,在员工登录功能中,我使用了简单的SELECT语句和WHERE条件,尽量避免了复杂的查询逻辑,提高了查询效率。
  • 数据库连接池配置优化:通过调整数据库连接池的参数,我优化了数据库的连接管理和资源利用。合理设置连接池大小、超时时间和连接复用策略,保证数据库连接的高效使用,避免了频繁创建和释放连接的开销。

数据库索引的原理,为什么要用 B+树,为什么不用二叉树?

  • 多叉树结构:B+树是一种多叉树,每个节点可以包含多个子节点。相比于二叉树,B+树可以存储更多的键值对,减少树的高度,从而减少磁盘I/O次数,提高查询效率。
  • 顺序访问性:B+树的叶子节点之间通过链表进行连接,可以支持范围查询和顺序访问。这对于数据库的范围查询非常重要,例如在使用BETWEEN、ORDER
    BY等操作时,可以通过顺序遍历叶子节点来快速获取有序的数据。
  • 层级结构和平衡性:B+树的节点之间通过层级结构和平衡性保持了较低的高度。这使得在最坏情况下,B+树的查询时间复杂度仍然是O(logn),保证了较稳定的查询性能。

limit 1000000 加载很慢的话,你是怎么解决的呢?

  • 分页加载:考虑将大数据集分成更小的分页进行加载,而不是一次性加载所有数据。可以使用LIMIT和OFFSET来指定每次查询的数据量和偏移量。
  • 使用索引:确保查询的列上有适当的索引,这将提高LIMIT语句的执行效率。通过分析查询执行计划,确定是否需要创建或优化索引以支持查询。
  • 数据缓存:如果数据不经常更新,并且具有较高的读取频率,可以考虑将查询结果缓存起来,以减少对数据库的频繁查询。缓存可以使用内存缓存(如Redis)或应用程序级别的缓存(如使用Spring Cache)来实现。

事务的隔离级别有哪些?MySQL 的默认隔离级别是什

么?

  • 读未提交(Read Uncommitted)
  • 读已提交(Read Committed)
  • 可重复读(Repeatable Read)
  • 串行化(Serializable)

Mysql 默认的事务隔离级别是可重复读(Repeatable Read)

什么是幻读,脏读,不可重复读呢?

  • 脏读:脏读指的是一个事务中读取了另一个事务未提交的数据。换句话说,事务A读取了事务B尚未提交的数据,而当事务B回滚时,事务A读取到的数据就是无效或错误的。脏读可能导致不一致的数据被读取和使用。
  • 不可重复读: 指的是在一个事务内多次读取同一数据时,得到了不同的结果。不可重复读通常发生在一个事务内多次读取同一数据,而其他事务在这期间进行了更新操作,导致读取到的数据发生了改变。
  • 幻读:,幻读指的是在一个事务中,第二次查询某个范围内的数据时,与第一次查询时不同的数据行出现了,就好像发生了幻觉一样。

幻读与新增或删除操作有关,涉及到数据范围的变化。
不可重复读与更新操作有关,涉及到同一数据的多次读取时的数据变化

一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

一般情况下,我们创建的表类型是InnoDB

不重启MySQL,如果新增一条记录,id是8;
重启,ID是6;因为InnoDB表只把自增主键的最大ID记录在内存中,如果重启,已删除的最大ID会丢失。

如果表类型是MyISAM,重启之后,最大ID也不会丢失,ID是8;

事务的四大特性

⑴ 原子性(Atomicity)
  原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

⑵ 一致性(Consistency)
  一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

⑶ 隔离性(Isolation)
  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  
⑷ 持久性(Durability)
  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

char 和 varchar 的区别是什么?

char的长度是固定的,varchar的长度的可变的;
char占用的空间比varchar大,但是char的效率比varchar的效率高;

float 和 double 的区别是什么?

(1)内存中占有的字节数不同

单精度浮点数在内存中占有4个字节;

双精度浮点数在内存中占有8个字节;

(2)有效数字位数不同

单精度浮点数有效数字8位;

双精度浮点数有效数字16位;

(3)在程序中处理速度不同

一般来说,CPU处理单精度浮点数的速度比双精度浮点数的速度快

如果不声明,默认小数是double类型,如果想用float,要进行强转;

常见错误:

1.float f = 1.3;会编译报错,正确的写法是float f = (float)1.3;或者float a = 1.3f;(f或F都可以不区分大小写)

2.java中3*0.1==0.3将会返回什么?true还是false?

答:返回false,因为浮点数不能完全精确的表示出来,一般会损失精度;

解决方案:

double a = 3 * 0.1;
double b = 0.3;
double epsilon = 1e-10; // 定义一个很小的误差范围

boolean result = Math.abs(a - b) < epsilon;
System.out.println(result); // 输出true

3.java中float f = 3.4;是否正确?

答:不正确。因为3.4是双精度浮点数,将双精度赋给单精度属于向下转型,会造成精度损失,因此需要强制类型转换float=(float)3.4;或者写成float f = 3.4f;

数据库如何保证主键唯一性

主键约束:在表的定义中,将主键列指定为主键,并对其应用主键约束。主键约束要求主键列的值是唯一且不为空的。当尝试插入或更新数据时,数据库会检查主键的唯一性,并阻止插入重复的主键值。
唯一索引:数据库会为主键列创建唯一索引。唯一索引是一种数据结构,它确保索引列的值是唯一的。当插入或更新数据时,数据库会检查唯一索引,如果发现重复的主键值,则拒绝插入或更新操作
自增主键:在许多情况下,数据库使用自增主键来保证主键的唯一性。自增主键是指主键列的值是自动递增生成的。数据库会自动为每个新插入的行分配一个唯一的主键值,确保不会出现重复的主键。

如何设计数据库

数据库设计时就要考虑效率和优化问题

  • 对于大数据量,可以采用粗粒度的数据表示方式,将相关数据合并到一个表中,并冗余一些必要字段,以减少表之间的关系和连接操作。这样可以降低查询的复杂度和开销

设计合理的表关联与约束

  • 若两张表之间的关系复杂,建议采用第三张映射表来关联维护两张表之间的关系,以降低表之间的直接耦合度。
  • 设计表时可以选择在系统编码阶段之后再添加约束关联,这是一种常见的做法。在设计阶段,有时可能会先关注表结构的设计和数据的存储,而不考虑约束关联。在系统编码阶段,你可以根据具体的业务需求和系统设计的要求,再来添加主外键等约束关联。这样可以灵活地根据系统的实际情况进行约束关联的定义,同时避免了在设计阶段可能出现的冗余或不必要的约束。

选择合适的主键生成策略

  • 自增主键(Auto-increment):这是最常见的主键生成策略之一。数据库系统会自动分配一个唯一的、递增的整数值作为主键。在插入新记录时,数据库会自动为主键列生成下一个可用的值。适用于高并发环境和大量插入操作。
  • GUID/UUID:全局唯一标识符(GUID)或通用唯一识别码(UUID)是由算法生成的字符串标识符,具有极低的碰撞概率,几乎可以保证全球范围内的唯一性。适用于分布式系统和需要生成唯一标识符的情况。
  • 组合主键(Composite Key):使用多个列的组合作为主键,通常是根据业务需求和数据模型的特点来定义。适用于关联表和多对多关系。
  • 业务主键(Business Key):使用业务上有意义的列作为主键,例如员工工号、订单号等。适用于需要根据业务标识来查询和操作数据的情况。
  • 外部主键(Surrogate
    Key):使用一个与业务无关的列作为主键,例如自动生成的唯一标识符。适用于需要保护业务数据的一致性和隐私性的情况

视图

数据库视图是数据库中的虚拟表格或虚拟查询结果集,它是基于一个或多个数据库表的查询定义而创建的。

存储过程

存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需要创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。

索引失效的几种情况?

1)like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效;

2)or 语句前后没有同时使用索引。当 or 左右查询字段只有一个是索引,该索引失效,只有左右查询字段均为索引时,才会生效;

3)在索引列上使用 IS NULL 或 IS NOT NULL操作。最好给列设置默认值。

4)对索引字段进行计算操作、字段上使用函数。

如何查询重复的数据

1、查询重复的单个字段(group by)

select 重复字段A, count() from 表 group by 重复字段A having count() > 1

2、查询重复的多个字段(group by)

select 重复字段A, 重复字段B, count() from 表 group by 重复字段A, 重复字段B having count() > 1

索引

索引怎么定义,分哪几种

唯一索引(Unique Index):唯一索引要求被索引的列的值在整个表中是唯一的。它可以确保被索引的列不包含重复的值,并提供了数据的唯一性约束。唯一索引可以加速查找和避免重复数据的插入。

主键索引(Primary Key Index):主键索引是一种特殊的唯一索引,它将表中的主键列与索引关联。主键索引要求主键列的值在整个表中是唯一的,且不允许为空。主键索引可以提供快速的主键查找和对表的高效访问。

非唯一索引(Non-Unique Index):非唯一索引允许被索引的列包含重复的值。它可以加速查找和范围查询,但允许重复值的存在。非唯一索引适用于经常进行查找和排序的列。

聚集索引(Clustered Index):聚集索引决定了表中数据的物理存储顺序。一个表只能有一个聚集索引,它通常与主键或唯一索引关联。聚集索引可以加速基于索引列的查找和范围查询。

辅助索引(Secondary Index):辅助索引(也称为非聚集索引)是基于非主键列创建的索引。它可以加速非主键列的查找和范围查询。表可以有多个辅助索引。

全文索引(Full-Text Index):全文索引用于对文本字段进行全文搜索。它可以提供高效的文本搜索和匹配功能,支持关键字搜索和相关性排序。

哪些情况索引会失效

  • 不适当的查询条件:如果查询条件中使用了不适合索引的操作符或函数,例如使用了LIKE操作符的前缀通配符(‘%value’)或使用了不可索引的函数操作,索引可能无法被利用,导致失效。
  • 列类型不匹配:当查询条件的列类型与索引列的类型不匹配时,索引可能无法被使用。例如,在索引为整数类型的列上进行字符比较。
  • 数据量过小:当表中的数据量非常小(通常低于索引的选择性阈值),查询优化器可能会选择全表扫描而不是利用索引,因为全表扫描的成本更低。
  • OR条件:对于包含多个OR条件的查询,如果其中的每个条件都无法利用索引,或者索引列与OR条件之间存在数据类型不匹配的情况,索引可能会失效。

索引不适合哪些场景

  • 数据量少的不适合加索引
  • 更新比较频繁的也不适合加索引
  • 区分度低的字段不适合加索引(如性别)

性别是否适合做索引

区分度不高的字段不适合做索引,因为索引页是需要有开销的,需要存储的,不过这类字段可以做联合索引的一部分。

mysql 的内连接、左连接、右连接有什么区别?

内连接,显示两个表中有联系的所有数据;
左链接,以左表为参照,显示所有数据,右表中没有则以null显示
右链接,以右表为参照显示数据,,左表中没有则以null显示**

redis

Redis 为何这么快?

1)基于内存;

2)单线程减少上下文切换,同时保证原子性;

3)IO多路复用;

4)高级数据结构(如 SDS、Hash以及跳表等)。

为什么单线程还读写性能这么高?

基于内存:Redis是基于内存的,内存的读写速度非常快;
上下文切换:单线程避免了不必要的上下文切换和竞争条件;
IO多路复用:底层采用NIO(非阻塞IO),NIO采用IO多路复用技术,一个线程通过多路复用器处理多个连接

说说你对Redis的了解/说说Redis的单线程架构

数据库:Redis是一款基于键值对的、线程安全的NoSQL数据库;
内存读写性能:它在内存中读写性能非常高,每秒可以处理超过百万次的读写操作。
服务端线程安全,客户端线程不安全:Redis服务端是线程安全的,永远只有主线程一个线程进行读写,不需要任何的同步机制。Redis客户端层面线程不安全,要引入原子指令(例如INCR是给数值原子性加1)、分布式锁、lua脚本保证Redis的原子操作。

Redis读写为什么不采用多线程?

CPU不是瓶颈:Redis在内存中读写性能非常高,CPU不是Redis的瓶颈,无需使用多线程。
担心加锁影响性能:多线程情况下,想实现线程安全必须加锁,加锁将极大地影响性能。

Redis的瓶颈:

内存:因为读写在内存中进行,内存大小会影响Redis性能。可以通过加内存、读写分离优化性能。
网络带宽:网络 IO是Redis最大瓶颈,也就是客户端和服务端之间的网络传输延迟。Redis6.0引入了网络IO多线程模型,提高了性能瓶颈。

详细的说说Redis的数据类型

Redis主要提供了5种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)。

Redis还提供了Bitmap、HyperLogLog、Geo类型,但这些类型都是基于上述核心数据类型实现的。5.0版本中,Redis新增加了Streams数据类型,它是一个功能强大的、支持多播的、可持久化的消息队列。

string可以存储字符串、数字和二进制数据,除了值可以是String以外,所有的键也可以是string,string最大可以存储大小为2M的数据。

list保证数据线性有序且元素可重复,它支持lpush、blpush、rpop、brpop等操作,可以当作简单的消息队列使用,一个list最多可以存储2^32-1个元素

hash的值本身也是一个键值对结构,最多能存储2^32-1个元素。

set是无序不可重复的,它支持多个set求交集、并集、差集,适合实现共同关注之类的需求,一个set最多可以存储2^32-1个元素

zset是有序不可重复的,它通过给每个元素设置一个分数来作为排序的依据,一个zset最多可以存储2^32-1个元素。

set与zset的区别为有序无序、底层存储结构、zset底层使用不使用红黑树

Redis 满足ACID吗

不完全满足ACID特性:Redis只满足隔离性和持久性,不满足原子性和一致性。
原子性:事务的所有操作,要么全部成功,要么全部失败。Redis不满足原子性,单个 Redis 命令的执行是原子性的,但事务失败后无法回滚。
一致性:事务前后,数据库的约束没有被破坏,保持前后一致。Redis连约束这个概念都没有。
隔离性:操作同一资源的并发事务之间相互隔离,不会互相干扰。Redis满足隔离性,因为redis server是单线程的,串行化执行事务,肯定是满足隔离性的。
持久性:事务的结果最终一定会持久化到数据库,宕机等故障也无法影响。Redis在开启aof并指定立刻持久化命令时,满足持久性。rdb模式会丢失部分数据,不满足持久性。

缓存三大问题以及解决方案?

缓存穿透:查询数据不存在
1)缓存空值

2)key 值校验,如布隆筛选器 ref 分布式布隆过滤器(Bloom Filter)详解(初版)

缓存击穿:缓存过期,伴随大量对该 key 的请求
1)互斥锁

2)热点数据永不过期

3)熔断降级

缓存雪崩:同一时间大批量的 key 过期
1)热点数据不过期

2)随机分散过期时间

如何保证 Redis 的高并发?

Redis 通过主从加集群架构,实现读写分离,主节点负责写,并将数据同步给其他从节点,从节点负责读,从而实现高并发。

Redis 如何保证原子性?

答案很简单,因为 Redis 是单线程的,所以 Redis 提供的 API 也是原子操作。

但我们业务中常常有先 get 后 set 的业务常见,在并发下会导致数据不一致的情况。

如何解决

1)客户端加锁;

2)使用 Lua 脚本实现 CAS 操作。

先删后写还是先写后删?

先删缓存后写 DB
产生脏数据的概率较大(若出现脏数据,则意味着再不更新的情况下,查询得到的数据均为旧的数据)。

比如两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

先写 DB 再删缓存
产生脏数据的概率较小,但是会出现一致性的问题;若更新操作的时候,同时进行查询操作并命中,则查询得到的数据是旧的数据。但是不会影响后面的查询。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值