面试基础问题

数据库

  1. 查询表中重复字段

SELECT Distinct(email) FROM Person A WHERE EXISTS(SELECT email FROM Person B WHERE A.email = B.email HAVING count(*)>=2)

  1. 索引原理, SQL执行策略

Mysql目前主要有以下几种索引类型:FULLTEXT, HASH, BTREE, RTREE

索引我们分为四类来讲 单列索引(普通索引,唯一索引,主键索引)、组合索引、全文索引、空间索引

最左前缀原则: 在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边, 比如复合索引(a, b, c)会作用于包含 a,b,c/a,b/a 的 where 语句, 因此 a 位置应该是查询最频繁的列

where 执行顺序是从左往右执行的,在数据量小的时候不用考虑,但数据量多的时候要考虑条件的先后顺序,此时应遵守一个原则:排除越多的条件放在第一个

from 语句多表做笛卡尔乘积, 从右往左, 小数量的表放在最右边

如何成功避开索引:

  • like语句,‘%w’不会使用索引,‘w%’会使用索引

  • 列类型为字符串类型,查询时没有用单引号引起来

  • 在where查询语句中使用表达式

  • 在where查询语句中对字段进行NULL值判断

  • 在where查询中使用了or关键字, myisam表能用到索引, innodb不行;(用UNION替换OR,可以使用索引)

  • where中复合索引未按顺序查询的

  1. 事务的实现

START TRANSACTION …. COMMIT …. ROLLBACK

ACID, 其中隔离性通过 锁 实现, 一致性通过 undo log 实现, 原子性和持久性通过 redo log 来实现

redo(重做) 和 undo(回滚) 比较:

  • 都是恢复操作: redo: 恢复提交事务修改的页操作/ undo: 回滚行记录到某个特定版本

  • 记录内容不同: redo: 是物理日志,记录的是物理的修改操作/ undo: 是逻辑日志,根据每行记录进行记录

  • 读取方式不同: redo : 在数据库运行时,不需要读取操作(注:数据库恢复时,才用redo)/ 在数据库运行时,需要随机读取(注:回滚时用)

两种隔离级别: READ COMMIT、REPEATABLE READ

A, B 账户各有 1000 元, 当 A 账户的某一个事务修改为 800 元并提交, B 账户的一个事务在提交前后读取到两个不同的 A 账户金额, 即: 同一个事务中查询结果未能保持一致

未解决上述问题, 使得当前事务每次读取的结果集都相同,而不管其他事务有没有提交, 但出现幻读的情形引出 MVCC

多版本并发控制: MVCC 是通过保存数据在某个时间点的快照来实现的

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID.

MySQL 锁:

读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。

行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。

  1. 优化点

为什么不使用 select *

  • 不需要的字段会增加数据传输的时间,即使mysql服务器和客户端是在同一台机器上,使用的协议还是tcp,通信也需要额外的时间。

  • select 可能会获取到自己不需要的列,如果以后表结构修改了,同样也可能会对代码产生影响。比如表增加了一个字段,而我代码与其对接的对象属性里没有这个字段,select 就会导致报错

  1. drop, truncate 和 delete

drop 删除表数据和定义, truncate 删除表数据, 不可 rollback, 使用的系统和事务日志资源少, delete 删除表数据, 按行删, 可接 where 语句, 可 rollback

  1. binlog

记录数据库增删改, 不记录查询的二进制日志, 用于数据恢复

语法

  1. 集合类的实现

TreeMap 红黑树, 默认按 key 升序遍历

HashMap threshold(最大容量) = len(初始化容量) * load factor(负载因子)

LinkedHashMap 双向链表加HashMap, accessOrder 为 false 维持 Key 的插入顺序遍历, 为 true 时可实现 LRU

网络

HTTP1.1的Pipeling方式本质是将请求串行化处理,后面的请求必须等待前面请求的返回才能被执行,一旦有某请求超时等,后续请求只能被阻塞,即线头阻塞;

HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;

数据结构

BST 二叉排序树, O(logN) 操作复杂度, 但是依赖于输入的建树顺序, 容易退化成 O(N)

AVL 平衡二叉排序树, 左右子树高度差小于1, 确保复杂度为O(logN)

B树家族, 多路搜索树, 为什么B+树适合索引: 数据存在叶节点, 每次检索都是到根到叶节点的过程, 其他节点存储的数据就是索引

  • 关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子结点,但是其只拥有 m-1 个关键字。

  • 存储的位置不同;B+ 树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。

  • 分支结点的构造不同;B+ 树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。

  • 查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。

BRT 红黑树, 自平衡二叉搜索树, 变色/左旋/右旋, 插入、删除、查找都是 O(logN)

框架

Bean 生命周期(模板答案): 通过工厂创建/通过上下文创建

  • 实例化一个 Bean(也就是 new 操作)

  • 设置 Bean 的属性值(也就是 IOC 注入, 从 xml 中读取, 调用 set 接口设置)

  • 如果 Bean 实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String) 方法(也就是设置 Bean 的名字)

  • 如果 Bean 实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory (设置 Bean 的创建工厂)

  • (上下文独有) 如果 Bean 实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法 (设置上下文)

  • 如果 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBeforeInitialization(Object obj, String s)方法(修改 Bean 内容, 还有 afterPropertiesSet 等等)

  • 如果 Bean 在配置文件中配置了 init-method 属性会自动调用其配置的初始化方法(调用 init-method 初始化方法)

  • 如果 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)方法

  • 如果 Bean 实现了 DisposableBean 接口,会调用那个其实现的destroy()方法;

  • 如果 Bean 配置了destroy-method 属性,会自动调用其配置的销毁方法(调用 destroy-method 销毁方法)

Bean 单例实现

发生在 AbstractBeanFactory 的 getBean -> doGetBean -> getSingleton 进行bean的创建, 双重判断加锁的单例模式

https://www.cnblogs.com/zhaoyan001/p/6365064.html

静态常量, 静态代码块线程安全, 但不是 lazy-init

getInstance 加锁, 单锁, 线程不安全

double-check 加锁, 线程安全

推荐静态内部类, 调用内部类时候才会创建

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {

private Singleton() {}

private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}

Spring 事务

spring支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中

异常处理

Service 层处理事务回滚(@Transactional), Controller 处理业务异常(通常自己封装Runtime) 然后用 @ControllerAdvice + @ExceptionHandler 进行友好展示

AOP的两种实现

SpringAOP 是 Spring 这个庞大的集成框架为了集成 AspectJ 而出现的一个模块

从实现上来讲

SpringAOP 是 基于代理实现的(Proxying), 可选两种方式

  • JDK动态代理(Dynamic Proxy)

    • 基于标准JDK的动态代理功能

    • 只针对实现了接口的业务对象

  • CGLIB

    • 通过动态地对目标对象进行子类化来实现AOP代理,上面截图中的SampleBean$$EnhancerByCGLIB$$1767dd4b即为动态创建的一个子类

    • 需要指定@EnableAspectJAutoProxy(proxyTargetClass = true)来强制使用

    • 当业务对象没有实现任何接口的时候默认会选择CGLIB

AspectJ 是 基于字节码操作(Bytecode Manipulation)

@Around -> @Before -> @After -> @AfterReturning

其中 Around 参数为 ProceedingJoinPoint, 需要调用 proceedingJoinPoint.proceed() 才可调用方法

I/O 与 进程

进程有内核态和用户态两种运行方式, 用户态可以使用 CPU 和内存来完成一些任务, 内核态可以对硬件外设进行操作(读取磁盘文件、发送数据网络)

I/O 模型

服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

  • 同步阻塞IO(Blocking IO):即传统的IO模型。

  • 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

  • 多路复用IO(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

  • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

同步和异步的区别在于多个任务和事件发生时,一个事件的发生或执行是否会导致整个流程的暂时等待

阻塞和非阻塞的区别在于当发出请求一个操作时,如果条件不满足,是会一直等待还是返回一个标志信息。

缓存

Redis与Memcached的区别与比较

  • Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。

  • Redis支持数据的备份,即master-slave模式的数据备份。

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中

  • Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。

Tair

  • MDB: 适用容量小(一般在M级别,50G之内), 读写QPS高(万级别)的缓存场景, 是内存型产品

  • RDB: 可持久化, Redis

  • LDB: 适用于确实有持久化需求,读写QPS较高(万级别)的应用场景, 目前使用的 SSD 硬盘, 基于开源的 LevelDB 引擎

  • PDB: 即将上线

  • FASTDUMP: ODPS 快速写入到分布式缓存Tair的解决方案

MDB: key 最大 1K, value 最大 1M, 是典型 slab 的最大限制(内存分配机制)

MDB 架构: client, 两台 ConfigServer(互为主备, 路由), 多个 DataServer(存储引擎)

两台 Configserver 互为主备。通过和 DataSrver 之间的心跳检测获取集群中存活可用的 DataServer,构建数据在集群中的分布信息(对照表)。

Dataserver 负责数据的存储,并按照 Configserver 的指示完成数据的复制和迁移工作。Client 在启动的时候,从 Configserver 获取数据分布信息,根据数据分布信息,和相应的 Dataserver 进行交互,完成用户的请求。

ConfigServer 的考点: 一致性 Hash

相邻的虚拟节点可能是多台不同的物理机,这样可以分散压力。无虚拟节点的话,只是简单的把压力转给另一台物理机

数据结构

如需转载,请注明出处