java面试(SQL篇)

一、数据库的事务性

1.数据库事务的四大原则ACID

  • A:原子性。数据库事务的操作是一个不可以分割的工作单位,要么全部发生,要么全不发生
  • C:一致性。事务开始以前和结束以后,数据库的完整性不会被破坏。完整性指的是关系数据的完整性和业务逻辑上的一致性。例如经典的转账案例,转账前后账户内的存款总额不能改变
  • I:隔离性。两个事务之间互相不受影响。
  • D:持久性,事务一旦执行完毕,生成的结果会被持久化保存在数据库,不会轻易回滚

 

2.事务之间是否会发生冲突?会产生什么结果

数据库事务之间的冲突一般提现在以下几个方面:

  1. 脏读:一个事务读取了另一个事务未提交的数据。事务A对数据进行了修改,未提交前,事务B读取了事务A修改的数据。但是事务A由于失败回滚了数据。此时事务B读取到的数据就是脏数据
  2. 不可重复读:事务A两次读取数据,但是在两次读取之间,事务B对于数据进行了更新操作,导致事务A两次读取到的数据不一致。
  3. 幻读:事务A对于数据库进行修改,同时事务B进行插入操作。导致事务A发现有没有修改的行。
  4. 更新丢失:事务A对于数据a是加一操作,事务B对于数据a是加一操作。两个事务同时对于a读取,然后AB前后次序操作。最终发现数据a的最终值为2.因为a的更新操作被B覆盖,导致更新丢失。

 3.如何理解事务的隔离级别?

为了在并发环境下实现数据库的一致性和完整性,我们引入隔离级别(TRANSACTION ISOLATION LEVEL)这个概念。

数据库的事务性个例是通过锁来实现的,通过阻塞事务来实现隔离的效果。不同的隔离级别是通过添加不同的锁,造成阻塞来实现。一般来说,安全级别越高,性能越低。

SQL Server实现了以下级别的事务性隔离:

image

  1. 未提交读:Read Uncommited (默认级别):包含未提交数据的读。
  2. 提交读:Read Connited:可以避免脏读。事务A的更新操作执行完毕之后才会执行事务B的读取操作
  3. 可重复读:Repeatable Read:允许进行幻读
  4. 可串行读:Serializable :最高级别隔离,都不允许

4.事务控制语法

BEGIN 或 START TRANSACTION 显式地开启一个事务;

COMMIT / COMMIT WORK二者是等价的。提交事务,并使已对数据库进行的所有修改成为永久性的;

ROLLBACK / ROLLBACK WORK。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;

SAVEPOINT identifier 在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;

RELEASE SAVEPOINT identifier 删除一个事务的保存点;

ROLLBACK TO identifier 把事务回滚到标记点;

SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE

5.什么是自动提交

 自动提交AUTOCOMMIT,是mysql默认的设置。如果不显式的开启一个事务,那么每个查询语句都会被当作单独的事务来处理。

6.数据库的存储引擎

数据库引擎是数据库底层软件结构。数据库引擎用于查询,更新,修改删除等操作。不同的数据引擎提供不同的存储结构,索引技巧等。mysql的核心是插件式引擎。

首选引擎:InnoDB。InnoDB是数据库首选引擎,它支持事务安全表(ACID)。5.5版本以后是默认的引擎,它支持锁定和外键。

对于innoDB,它的索引一定包括自动增长列。如果是组合索引,自动增长列也必须是第一行。但是对于MyISAM引擎,自动增长字段可以是组合引擎的其他列

innoDB和MyISAM的区别

å¼æ对æ¯

 二、数据库的索引

1.什么叫做索引,为什么要实现索引呢?

对于关系型数据库,数据库本身以及所处服务器的性能会极大的影响数据库的效率。当数据库的容量很高,又或者并发量达到一定数量的话,我们可以引入索引来查询数据库,已达到更快查询的目的。索引是指对某一列或者多列的数据进行排序的一种结构,根据索引进行查询可以有效提高效率。

2.索引都有哪些种类呢 ?

 索引分为以下三种:

  1. 聚集索引。聚集索引又叫做主键索引,所有的数据库都是按照主键进行排序查询的。
  2. 非聚集索引。给普通的字段加上的索引。例如给姓名加上索引,查询的时候会按照姓名排序进行查询
  3. 联合索引。多个字段组成的索引,例如:
    key 'idx_age_name_sex' ('age','name','sex')

    联合索引遵从最左前缀原则。
    最左前缀原则:最左边的字段优先,只要是从第一个索引开始,按照索引顺序连续的字段都能走索引。
    例如
    select * from user where age=1 and name="王总“ and sex=1
    这种三个查询字段都会走索引.字段只要包含即可按照对应搜索,与顺序无关,例如:
    select * from user where name="王总“ and sex=1 and age=1  也会走索引
    但是
    select * from user where age=1 and  and sex=1不会走索引,因为查询语句不包含name字段,所以不是连续的索引字段。
    一般来说联合索引用的最多

 3.索引设计原则

  1. 对于经常查询的字段,建议做索引
  2. 索引并不是越多越好,一个表如果有大量的索引,不仅占用空间和资源,还会影响插入更新删除等操作的效率。
  3. 避免对经常更新的表建立索引,因为索引也要更新,影响效率。
  4. 数据量小的表不适合建立索引,数据量大的表用索引可以有效提高效率。数据量小反而导致维护索引结构浪费资源。
  5. 区分度低的字段不适合建立索引,比如性别,状态等字段
  6. 在本身具有唯一性的字段上简历唯一性索引,能有效的提高查询效率。
  7. 频繁进行跑排的字段(group by/order by)上建立组合索引能有效的提高效率;

4.什么情况会出现索引未命中的情况

1.在使用范围查询,例如>;<;!=;between and等

  • >,<这类范围查询如果是查询的范围较小,索引仍然有效。范围过大的话导致无效
  • !=是个很大范围的查询
    |
  • between and
  • like如果以%开头的话,不走索引
  • 不符合最左前缀顺序的查询语句
  • 使用函数不能命中索引
  • 类型不一致 如果列是字符串类型,传入条件是必须用引号引起来
  • order by :当根据索引排序时候,select查询的字段如果不是索引,则速度仍然很慢

  • text类型,必须制定长度,否则无法命中:

 三、读写分离设计

1.什么叫读写分离

所谓的读写分离是针对数据库性能优化进行的。当数据库的数据量到达一定的数量,或者数据库的访问量很大的时候,数据库的性能会受到很大影响。读写分离指的是通过把select操作和update,insert,delete等操作分离开来,从而减少数据库的处理数据的压力。进而对数据库的查询性能有很大的改善。所以说,读写分离大部分是针对于读操作的,对于读取数据的性能有很大提升。读写分离是数据库集群的一种模式

2.读写分离数据库结构怎么设置

读写分离要求有一个主库和一个或者多个从库,我们称之为master和slave;进行写操作都是在master上面实现的,而所有的读操作都是在从数据库上面实现的。主数据库进行写操作之后会将数据同步到读数据库,同步是通过binlog实现的。主数据库更新的内容会写入到二进制日志文件binlog中,而从数据库会通过binlog实现同步。

3.读写分离会出现哪些问题呢

 读写分离的延迟。首先读写分离通过binlog同步文件的话大概会有1s的延迟,如果有大量的数据更新的话延迟会更多。所以可能出现刚更新的数据查询从库的时候查询不正确。比如经典的付款。如果付款完了,在查询一下,但是此时的数据还没有更新,就不能正确的判断是否付款成功。解决方法有两个:一是如果在从数据库查询不到或者查询错误相关的数据再去主数据库查询一下。但是这样可能导致有人利用漏洞大量查询不存在的数据而导致写数据库崩溃。二是业务逻辑中,重要的操作或者密切相关的操作都从master查询,这样可以有效的避免同步延迟,我们的项目就采用的这种方法。

4.分配机制问题

 1.从代码实现分离。我们的项目采用mybatis管理数据库,所有的read和write都是各有一套代码和服务,配置有各自的连接池。通过业务逻辑判断是调用读还是写方法。

2.利用中间件,比如Mysql Proxy,Mysql Route,Atlas等,但是中间件的开发比较复杂,而且中间件本身占用了一定的资源。所有数据库都要依赖于这个中间件,一旦中间件出问题的话,会导致系统宕机。

四、分库分表设计 

1.为什么要分库分表

首先初步的读压力我们可以通过读写分离来实现,但是当数据库的数据量到达一定的程度后,比如说数据超过了几千万条。这些限制查询效率不再是读而是写。这时候读写分离对于写入数据的性能不能有很大提升。这时候就需要进行分库分表,把一个表拆成多个表,一个库拆成多个库来环节数据库压力。

2.分库分表原则

  1.  能不分就不分:mysql是关系型数据库,关系型数据库体现了业务逻辑的关系,数据库如果设计的过于复杂,相应的业务逻辑会更加的复杂。我们设计的原则是尽量减少业务逻辑的复杂性,所以如果性能不够的话我们优先考虑升级硬件,读写分离,利用缓存等技术来优化数据库性能
  2. 当数据库数据量过大影响正常的运维时,应该考虑分库分表:正常的运维包括:
    数据库备份;
    数据表修改;
    热点数据;
  3. 数据库设计不合理,需要考虑垂直拆分:例如某个表的字段不断的呗update;或者存在text等大数据字段
  4. 数据库出现无穷增长字段,需要考虑分库分表,水平拆分:
    指的是例如流览足迹,用户日志等表

3.分表设计策略

  1. 垂直拆分:垂直拆分指的是对于一张表,我们把一些冗余字段拆开单独存储。比如订单表的物流信息。如果都放在订单表,订单表会多很多个字段。最好的方式把这些关联的单独生成关联表,避免表里存储太多字段信息。垂直分表是基于数据库中的列进行,某个表字段较多,可以新建一张扩展表,将不经常用或者字段长度较大的字段拆出到扩展表中。在字段很多的情况下,通过大表拆小表,更便于开发与维护,也能避免跨页问题,MYSQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的开销。另外,数据库以行为单位将数据加载到内存中,这样表中字段长度越短且访问频次较高,内存能加载更多的数据,命中率更高,减少磁盘IO,从而提升数据库的性能。
     
  2. 水平拆分:水平拆分指的是将表的数据分开,生成多个结构相同的表。
    ----Hash取模:最简单的策略是通过id取模来确定放在哪个表。比如对于一个表我们把它拆成了3个表,这样我们首先要有id生成策略,id不能是自增而是要按照一定规则生成(这里推荐uuid),生成的uui按照3取模,获取的余数来确定是放在哪个表里面的。这样的缺点在于一旦把表的拆分数写死的话,不利于后序的扩展,但是优点在于分表策略简单易行。
    ----range:通过范围来分表,比如id是0-10000的放在一个表,10000-20000的放在一个。。。这样的优点是便于修改节点范围值
    ----热度:冷热分离,将6个月(或者其他时间段)以前的数据放到别的表,最近的数据放到不同表

 

五、缓存

1.什么是缓存

提到数据库就不得不提到缓存,数据库是关系型数据库,是基于硬盘的存储,所有的数据存储在硬盘中,读取时到硬盘中读取。而缓存指的是存储在内存中的内容。所以缓存读取都优于直接存储。目前项目中用缓存有两种方式,一是数据库的缓存,数据库自带的缓存,可以将部分数据加载到缓存中,读取的时候从缓存读取。另一中是第三方缓存服务器,最常用的redis。我们缓存用的方式一般是启动时将数据加载到redis,调用时直接从redis拿取数据,这样可以有效的提高读取数据的效率。

2.redis的优势

 Redis是目前主流的内存数据库之一。相较于其他的缓存,redis缓存具有以下优点:

  1. 支持数据的持久化,可以将数据保存在磁盘中,重启时再次根据需要加载
  2. 支持多种数据结构的存储而不是简单k-v结构
  3. 支持数据库的备份,即master-slave模式的备份
  4. 性能极高,由于独特的设计优势,redis的性能极高
  5. redis支持原子性操作,也支持事务性管理
  6. redis支持消息队列,通知,过期key等设置

3 .redis都有哪些数据结构?如何存储数据呢

  1. 首先是最基本的key-value值。此模式可以存储简单的string等字段,一般来说验证码验证和登录验证都可以通过这种方式。String类型是安全的,一些图片和对象的序列化数据可以通过这种方式存储。String是最基本的数据类型,redis中String的最大容量是512m。
  2. Redis中的hash类型是一个键值对映射的列表,类似于Map<String,Object>。hash类型是储存对象的最好选择。
  3. List:Redis中的列表按照插入的顺序排序。底层实现是链表,所以可以头插或者尾插。
  4. Set:Redis中set是String类型的集合,不可重复。底层实现是hashtable
  5. Zset:Zset同样是set集合,与set的区别是是有序的。redis会为每一个string数据匹配一个double类型的分数,double可以重复,但是string不能重复。然后通过double给数据排序

4.什么是redis持久化,redis持久化有哪些方法

 redis的内容是存在内存中的,一单系统宕机,有可能造成redis丢失信息。redis持久化指的是可以将缓存中的内容存储于磁盘之中,以避免数据的丢失。redis持久化有AOF和RDB(默认)两种。

  1. RDB:Redis DataBase;通过核心函数rdbSave从内存中将rdb文件存储进磁盘,通过rdbLoad函数加载文件到内存中。
  2. AOF:Append-Only file;每当系统执行服务器定时任务或者函数的时候flushAppendOnlyFile 都会被调用。这个函数有两个作用:
    Write:把aof_buf中的内容写入到aof文件
    Save:把aof文件通过fsync 或者fdatasync 保存到磁盘之中
    特点:内容是redis通讯协议(RESP )格式的命令文本存储;aof的更新频率高于rbd,安全性也大于rbd,但是效率低于rbd。

 5.什么是RESP协议?

RESP协议指的是redis客户端和服务器之间的一种通讯协议,特点是开发简单,可读性高,可以进行快速解析;协议的内容包括:

  • For Simple Strings the first byte of the reply is "+" 字符串:返回类型如果是String的话,会以“+”开头,随后紧跟内容字符串,然后以CRLF结尾,例如:
    +OK\\r\\n
  • For Errors the first byte of the reply is "-" 错误:返回是错误的话,以“-”开头,后面跟错误类型,然后是错误的描述语句,例如:
    -ERR unknown command 'foobar'

     

  • For Integers the first byte of the reply is ":" 整数:返回整数以“:”开头,紧跟数字,最后CRLF结尾。例如:
     

    :1800\\r\\n
  • For Bulk Strings the first byte of the reply is "$" 字符串:bulk String是一种二进制安全的字符串结构,首先以“$”开头,后面跟位数,然后CRLF,之后是真正的内容,以后以CRLF结尾.注意,空格也算位数。例如:
     

    $4\\r\\ntest\\r\\n

     

  • For Arrays the first byte of the reply is "*" 数组:array类型的数据以“*”开头,后面紧跟数组的长度,然后CRLF结尾。之后的内容是数组的内容,可以是字符串或者数字。例如(实例中的分行只是为了展示,实际上这些字符都是连在一起的):
     

    *2\\r\\n
    $4\\r\\ntest\\r\\n
    $3\\r\\nliu\\r\\n

     

 

 6.redis有哪些架构?都有什么特点

  1. 单机版本:只有一个redis服务器,多个客户端向通过一个服务器请求:

    特点:简单
    缺点:无法承受高并发环境的压力,内存有限,处理能力也有限
  2. master-slave模式(主从模式):一个master复制多个slave,slave的数据与master同步。

    特点:能缓解master的读数据的压力,master和slave的数据保持一致
    缺点:无法高可用,未能缓解master写数据的压力
  3. 哨兵模式:Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移,他有三个特性:
    监控(Monitoring):    Sentinel  会不断地检查你的主服务器和从服务器是否运作正常;
    提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知;
    自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作;

    特点:保证高可用性,有效的故障转移策略,可以对各个节点进行监控
    缺点:主从模式,没有缓解写数据的压力;发生故障时切换需要时间,可能导致丢数据。
  4. 集群(proxy 型):Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。

    特点:多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins ;
    支持失败节点自动删除;
    后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致。
    缺点:增加了新的 proxy,需要维护其高可用。
  5. 集群直连:

    特点:

    1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。

    2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。

    3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。

    4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本

    5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。

    缺点:

    1、资源隔离性较差,容易出现相互影响的情况。

    2、数据通过异步复制,不保证数据的强一致性

7.谈谈对redis单线程的理解 

redis的单线程指的是网络请求模块是单线程的,其他的模块依然是多线程的。
redis使用单线程的原因是cpu并不是限制redis性能的主要方面,单线程和多线程对于速度的提升不是很大。并且不实用单线程可以减少不必要的上下文切换和竞争锁。redis的速度提升主要在于I/O多路复用设置。I/O模型在当前没有任务的时候会进入阻塞的状态,一单有任务才会被唤醒。博文可以参考如下:
https://blog.csdn.net/chenyao1994/article/details/79491337

 8.redis用的那种I/O模型,有什么有点,你了解哪些I/O模型?

  1. 阻塞I/O:
    一般来说常见的I/O模型都是阻塞的,阻塞指的是读写命令不完成指标就不返回。比如套接字模型,当我们发出一个指令让它读取n个字节的时候,如果缓存区没有足够的数据供它读的话,这时候读的命令会被阻塞,知道有新的命令请求到来或者链接关闭,这个读的命令才能结束。这种情况下线程会一直被阻塞。一般来说写是不会被阻塞的,除非为写分配的缓冲区已经被写满了,直到有空余的空间写的阻塞才会被解除
  2. 非阻塞I/O:
    相对于阻塞I/O,非阻塞模型更加的灵活。对于读方法来说,非阻塞模型是能读多少读多少,能写多少写多少。如果读不到数据会返回错误的标识,进程会反复调用读的操作,直到读到数据进行处理。这种情况下读写数据都会通过告诉读写了多少数据来让客户端进行判断,线程不必在阻塞等待,读写也会立即完成,提高了效率
  3. 多路复用:
    多路复用的模型在java中应用很广泛,比如NIO,Redis都用的多路复用模型。多路复用的最大特点就是事件轮询。多路复用的模型中,只需要开启一个线程,反复询问socket的状态来询问是否需要读写。比如你要读取一个数据,但是发起读的时候数据没有准备好,这时候你不必阻塞,而是单独有一个线程负责阻塞,一旦有数据更新到来的时候就通知你进行读写。
    最简单的多路复用模型是select函数。select函数的容量有限只有1024,可以监听三类描述符:writefds(写状态), readfds(读状态), exceptfds(异常状态)。我们在select函数中告诉内核需要监听的不同状态的文件描述符以及能接受的超时时间,函数会返回所有状态下就绪的描述符的个数,并且可以通过遍历fdset,来找到就绪的描述符。限制是每次每次都要把带监控的描述符从用户态拷贝到内核,开销会很大,并且每次调用select都要轮询所有的描述符查看就绪状态。
    其次还有poll函数,相比于select,poll采用自己的结构pollfd,pollfd结构包括了events(要监听的事件)和revents(实际发生的事件)。而且也需要在函数返回后遍历pollfd来获取就绪的描述符。但是没有最大文件数限制。
    epoll针对以上select和poll的主要缺点做出了改进,

    主要包括三个主要函数,epoll_create, epoll_ctl, epoll_wait。

    epoll_create:创建epoll句柄,会占用一个fd值,使用完成以后,要关闭。
    int epoll_create(int size)
    epoll_ctl:提前注册好要监听的事件类型,监听事件(文件可写,可读,挂断,错误)。不用每次都去轮询一遍注册的fd,而只是通过epoll_ctl把所有fd拷贝进内核一次,并为每一个fd指定一个回调函数。
    当就绪,会调用回调函数,把就绪的文件描述符和事件加入一个就绪链表,并拷贝到用户空间内存,应用程序不用亲自从内核拷贝。类似于在信号中注册所有的发送者和接收者,或者Task中注册所有任务的handler。
    epoll_wait:监听epoll_ctl中注册的文件描述符和事件,在就绪链表中查看有没有就绪的fd,不用去遍历所有fd。
    相当于直接去遍历结果集合,而且百分百命中,不用每次都去重新查找所有的fd,用户索引文件的事件复杂度为O(1)

8.什么是redis分布式锁,谈谈你的理解

 分布式锁:原理是通过状态值标识锁,获取锁和释放锁都要改变对应的状态值。redis作为单进程单线程的程序,通过队列将并发访问变为串行访问,并且各个客户端之间并不存在竞争关系。
关键命令

> SETNX key value
# SETNX: SET if Not eXists
# 将key的值设置为value
# 当key存在时,不做任何操作,返回   0
# 当key不存在时,进行设置操作,返回 1

> GET key
# 返回key的值

> GETSET key value
# 返回key的旧值,同时将key的值设置成value

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

9.什么是缓存穿透?如何避免?

 一般来说如果按照key值查询不到对应的value的话,一般都会去后台系统DB中查询数据。这样,会有恶意攻击故意去查询不存在的数据,导致后台服务器系统压力过大最终崩溃。这被称为缓存穿透。
解决方法:

  1. 将查询过多的参数放到一个bitmap中,查询时首先通过bitmap过滤掉恶意数据;
  2. 将查询过多的key(即时不存在)也生成一个空对象存到缓存中。这种情况可能导致业务逻辑的不一致性

10.什么是缓存雪崩,如何解决? 

缓存雪崩指的是缓存层发生故障,或者某些大规模访问的key值同一时间失效,导致所有的请求都请求到存储层,导致存储层也崩溃。

解决方法:

  1. 实现之前提过的redis多可用性,做集群;
  2. 限流:对某个关键字实现加锁,即对于某个key只安排一个线程进行读写,其他线程等待;
  3. 数据预热:对于可能大规模访问的数据部署的时候加载到缓存中,设置失效时间尽可能均匀,以避免缓存雪崩

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值