一、对数据库的查询性能优化,有什么策略?
1.SQL语句层优化
根据慢查询日志,找出需要优化的一些语句。
启动慢查询日志方法:
1.开启慢查询日志
1.1使用SQL命令设置系统变量:SET GLOBAL slow_query_log = ‘ON’;,开启日志记录功能
1.2默认日志文件通常位于 /var/lib/mysql/ 目录下(具体路径可通过 SHOW VARIABLES LIKE ‘slow_query_log_file’; 查询)
2.配置执行时间阈值
2.1设置 long_query_time 参数(单位:秒):例如 SET GLOBAL long_query_time = 2;,表示超过2秒的SQL将被记录
2.2默认阈值为10秒,但生产环境建议设为1秒或更低以提高敏感度
3.手动查看日志内容
3.1通过命令行工具(如 tail)实时监控日志:tail -f /var/lib/mysql/slow.log,可动态显示新增条目
3.2或使用文本编辑器(如 vi)静态查看:vi /var/lib/mysql/slow.log
日志包含SQL语句、执行时间等细节,示例:执行时间超过阈值的SQL会明确标注(如 Query_time: 12)
4.分析慢查询原因
4.1使用 EXPLAIN 工具解析SQL执行计划:针对日志中的慢SQL运行 EXPLAIN [SQL语句],检查索引使用情况(如 type 扫描方式、rows 扫描行数)
4.2正则表达式清洗日志:替换查询条件变量为占位符(如将具体值改为 X),聚合相同业务SQL以统计平均耗时
4.3推荐工具:MySQL自带的 mysqldumpslow 或第三方工具(如 Percona Toolkit)解析日志,快速定位高频慢SQL
常见优化方向:
1.避免SELECT *,只查询必要的字段
2.避免在SQL中进行函数计算等操作,是的无法命中索引
3.避免使用%LIKE,导致全表扫描
4.注意联合索引需要满足最左匹配原则
5.不要对无索引字段进行排序操作
6.链表查询时需要注意不同字段的字符集是否一致,不一致也会导致全表扫描
2.数据库表设计优化
1.合理的表结构:比如选择合适的数据类型,例如能用int就不要用bigint,还有varchar和char的选择等
2.合理冗余字段:在适当的情况下进行反范式化设计,冗余部分数据,减少关联查询
3.索引优化:根据查询频率和条件,创建合适的索引,删除不必要的索引,因为索引维护也需要成本。建议使用EXPLAIN分析查询执行计划,确认是否用上索引,是否用对索引
4.分库分表:对于超大规模的数据库系统,可以采用分库分表策略,将数据拆分到多个数据库或表中,提高读写性能和扩展性。
二、假如数据库中有一张表,存储的是现场施工人员的定位信息,以坐标为编码,有一个轨迹编码的字段。例如施工人员从A地走到B地,中间所有的坐标都会连接成一条线,A到B的距离归为一条轨迹,如果有百万级的数据量,针对所有的施工人员,查询出人员每天轨迹的矩阵图,展示在地图上,有什么方式使得查询的效率更高?
1.核心优化策略
- 空间索引加速查询
a. PostGIS(PostgreSQL插件):对坐标字段创建GIST索引
CREATE INDEX idx_location ON worker_trajectory USING GIST (coordinate);
b. MySQL 8+:使用SPATIAL INDEX
ALTER TABLE worker_trajectory ADD SPATIAL INDEX(coordinate);
c. 轨迹数据预聚合
-- 按天+人员+轨迹ID聚合为LineString
CREATE TABLE daily_trajectory AS
SELECT
worker_id,
trajectory_id,
DATE(timestamp) AS date,
ST_MakeLine(coordinate ORDER BY timestamp) AS path -- PostGIS函数
FROM raw_trajectory
GROUP BY worker_id, trajectory_id, DATE(timestamp);
d. 时空分区表
-- PostgreSQL 按日期分区
CREATE TABLE trajectory_2025 (
PARTITION OF worker_trajectory
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01')
);
2.查询优化技巧
2.1 简化轨迹点数(降低传输量)
-- PostGIS 使用Douglas-Peucker算法压缩
SELECT ST_Simplify(path, 0.0001) FROM daily_trajectory;
2.2 分页加载轨迹
SELECT path FROM daily_trajectory
WHERE date = '2025-07-31'
ORDER BY worker_id
LIMIT 100 OFFSET 0; -- 分批请求
2.3 空间范围过滤
-- 只查询地图当前视口内的轨迹
SELECT * FROM daily_trajectory
WHERE path && ST_MakeEnvelope(minX, minY, maxX, maxY); -- 地图边界框
3.还可以使用redis数据结构增加查询效率
| 数据类型 | 用途 | 优势 | 示例内存占用 |
|---|---|---|---|
| List | 存储压缩轨迹点 | 顺序访问快,支持批量操作 | 50点轨迹≈300字节 |
| GEO | 实时位置查询 | 原生支持半径搜索 | 每点16字节 |
| Sorted Set | 热点轨迹空间索引 | 范围查询高效 (ZRANGEBYSCORE) | 每成员64字节 |
| HyperLogLog | 访问量统计 | 百万计数仅需12KB | 固定12KB |
| Hash | 轨迹元数据存储 | 快速存取字段 | 每字段100字节 |
4.总结
- 立即添加空间索引 + 按日分区表
- 夜间批处理生成聚合轨迹表
- 前端启用Canvas/WebGL渲染并设置视口过滤
- 长期规划流处理架构替代批处理
三、java中常见的垃圾回收算法
- 标记-清除算法(Mark-Sweep)
- 工作原理:先遍历堆中的对象,标记出所有的存活对象,接着清除未标记的对象。
- 优点:实现简单,能够处理堆中的所有对象。
- 缺点:标记和清除的过程会产生内存碎片,影响后续内存分配的效率。
- 标记-整理算法(Mark-Compact) :
- 工作原理:首先标记出所有存活的对象,然后将存活的对象整理到一边, 后清除未标记的对象。
- 优点:避免了内存碎片问题。
- 缺点:整理阶段需要移动对象,导致额外的开销。
- 复制算法(Copying) :
- 工作原理:将内存分成两部分,每次只使用其中一半,垃圾回收时将存活的对象从一半复制到另一半,清除原区域的所有对象(朴素的复制算法是这样的,实际使用会分为两个survivor和一个eden区)。
- 优点:无处理内存碎片,分配效率高。
- 缺点:需要双倍的内存空间,浪费了一 半的空间。
四、如何合理的规划新生代、老年代的生命周期
为提高垃圾回收效率,根据对象的生命周期特点来进行优化。
- 对象的生命周期特点
- 大多数对象存活时间短:大部分对象会很快变成垃圾,不再被使用,这些短生命周期的对象会被分配到新生代。
- 少部分对象存活时间长:一些长期存活的对象不会很快被回收,分配在新生代的对象经过多次垃圾回收仍然存活,将晋升到老年代。
所以按照存活时间分区管理更加高效,也因为不同分区的生命周期不同,所以可以采用不同的清除算法来优化处理。
- 不同的回收算法:
- 新生代的回收:新生代通常采用复制算法,因为新生代中大部分对象生命周期短,大部分会在一次GC中被回收,复制算法只需要在内存中保留少量存活对象,并将它们复制到Survivor空间,回收剩余区域。这种算法效率很高,适合新生代频繁创建和回收的特点。
- 老年代的回收:老年代中对象存活时间长,回收频率低,使用标记-整理算法 或 标记-清除算法,更适合老年代对象的特性。
且分区后可以减少GC暂停的时间(你想想每次处理一个对的数据,还是将堆分区处理来的快)。
总而言之,分区是为了更高效的管理不同生命周期的对象。
- 堆的分代机制
java堆内存根据对象生命周期被划分为三部分:- 新生代(Young Generation): 存放新创建的对象
- 老年代(Old Generation): 存放存活时间较长的对象,通常是从新生代晋升过来的对象
- 永久代(Metaspace)(JDK 8以前为永久代,JDK8之后为元空间):存放类的元数据信息,包括类的静态变量、方法等
- 新生代结构
新生代进一步划分为三个区域:- Eden区:所有新创建的对象首先被分配到Eden区
- Survivor区:Eden区中的存活对象会被复制到Survivor区(一般分为两个区域,S0和S1),经过多次GC存活的对象会逐渐晋升到老年代。
新生代中采用复制算法,每次垃圾回收时,将Eden和Survicor中的存活对象复制到另一个Survivor空间,效率高且避免内存碎片化。
- 老年代的作用
老年代用于存放生命周期较长的对象,通常是新新生代晋升过来的,老年代使用的回收算法不同于新生代,常用标记-清除算法或标记-整理算法,适合回收长生命周期的对象。
五、单例模式实现方式?
1.单例模式常见有:饿汉式、懒汉式(线程安全版需要加双重检查锁定)、静态内部类、枚举单例(Java特有)等几个实现方式。
- 饿汉式:实例在类加载时就创建,线程安全,但是如果实例初始化较多或没有被使用会浪费资源。
- 懒汉式:实例在首次访问的时候创建,节约资源,但需要确保线程安全。
- 双重检查锁定:在懒汉式的基础上优化,直接加锁效率太低了,双重检查锁只在第一次检查实例为null时加锁,提高性能。
- 静态内部类:利用类加载机制实现懒加载和线程安全,推荐使用。
- 枚举单例(java特有):通过枚举实现单例,简单且防治反射和序列化攻击
2.如何保证线程安全?
推荐静态内部类或双重检查锁定,配合volatile
3.为什么需要双重检查锁定?
因为懒汉式单例在多线程环境下,可能出现多个线程同时初始化实例的问题。
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
不过每次调用getInstance方法都需要加锁,实际上只需要在第一次创建实例时加锁。在高并发环境下,频繁获取单例对象的锁操作会显著降低性能。
而双重检查锁定通过减少加锁的范围,避免每次获取实例都加锁的问题,提升性能。
public class Singleton {
private static volatile Singleton instance; // 使用 volatile 防止指令重排
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查:避免不必要的同步
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查:确保实例唯一
instance = new Singleton();
}
}
}
return instance;
}
}
4.为什么双重检查锁定需要volatile关键字
在java中,volatile修饰符用于防指令重排序,从而确保双重检查锁定的正确性。
instance = new SingleTon()是一个非原子操作,分为三步:
- 1.初始化内存空间
- 2.初始化对象
- 3.将对象的引用赋值给instance
在没有volatile的情况下,编译器和CPU可能对这些步骤进行重排序(比如执行顺序变成1 - 3 - 2)。此时,另一个线程可能会在instance被赋值后,但是对象尚未完成初始化时访问它,从而导致错误。
所以将instance声明为volatile,可以禁止指令重排序,确保对象的初始化过程对所有线程可见。
private static volatile Singleton instance;
保证instance的写操作对其他线程立即可见,并禁止重排序优化,确保双重检查锁定的准确性。
总结:
- 双重检查锁定通过缩小加锁范围,仅在必要时同步代码块,既保证线程安全,又提升性能。
- volatile关键字是实现双重检查锁定的关键,防止指令重排序导致未初始化对象被访问。
饿汉式(线程安全,类加载时初始化)
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
懒汉式(线程不安全,需改进)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查(线程安全,推荐)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类(线程安全,推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
枚举单例
public enum Singleton {
INSTANCE;
public void bizMethod() {
// 一些业务逻辑方法
}
}
//使用
Singleton singleton = Singleton.INSTANCE;
singleton.bizMethod();
六、列举常用的Spring MVC注解
- 核心请求映射注解
| 注解 | 功能描述 | 示例 |
|---|---|---|
| @RequestMapping | 通用请求映射支持所有HTTP方法 | @RequestMapping(“/users”) |
| @GetMapping | GET 请求映射 | @GetMapping(“/user/{id}”) |
| @PostMapping | POST 请求映射 | @PostMapping(“/create”) |
| @PutMapping | PUT 请求映射 | @PutMapping(“/update/{id}”) |
| @DeleteMapping | DELETE 请求映射 | @DeleteMapping(“/delete/{id}”) |
| @PatchMapping | PATCH 请求映射 | @PatchMapping(“/partial-update”) |
- 请求参数处理
- URL 参数绑定
| 注解 | 功能描述 |
|---|---|
| @PathVariable | 路径变量绑定,从URI模板中提取值 @GetMapping(“/user/{id}”) public User get(@PathVariable Long id) |
| @RequestParam | 查询参数绑定,获取URL?后的参数 @RequestParam(“name”) String username |
| @RequestParam(带默认值) | 带默认值的参数 @RequestParam(defaultValue = “0”) int page |
- Header/Cookie 处理
| 注解 | 功能描述 |
|---|---|
| @RequestHeader | 获取HTTP头信息 @RequestHeader(“User-Agent”) String agent |
| @CookieValue | 获取Cookie值 @CookieValue(“JSESSIONID”) String sessionId |
- 请求体处理
| 注解 | 功能描述 |
|---|---|
| @RequestBody | 解析请求体将JSON/XML自动转为对象 @PostMapping public User create(@RequestBody User user) |
| @RequestPart | 处理文件上传 @RequestPart(“file”) MultipartFile file |
- 响应处理注解
| 注解 | 功能描述 |
|---|---|
| @ResponseBody | 直接返回数据不经过视图解析器 @ResponseBody public User getUser() |
| @RestController | 组合注解= @Controller + @ResponseBody @RestController public class UserController |
| @ResponseStatus | 自定义HTTP响应状态码 @ResponseStatus(HttpStatus.CREATED) |
| @ResponseEntity | 完整控制响应 包含状态码/头信息/响应体 return new ResponseEntity<>(user, HttpStatus.OK) |
- 模型与视图处理
| 注解 | 功能描述 |
|---|---|
| @ModelAttribute | 模型数据绑定 1. 方法级:每次请求前执行 2. 参数级:绑定表单数据 @ModelAttribute(“user”) public User preload() |
| @SessionAttributes | 将模型数据存储到Session @SessionAttributes(“cart”) |
| @SessionAttribute | 从Session中获取数据 @SessionAttribute(“cart”) ShoppingCart cart |
- 异常处理
| 注解 | 功能描述 |
|---|---|
| @ControllerAdvice | 全局异常处理定义全局异常处理器 @ControllerAdvice public class GlobalExceptionHandler |
| @ExceptionHandler | 处理特定异常 @ExceptionHandler(UserNotFoundException.class) |
| @RestControllerAdvice | = @ControllerAdvice + @ResponseBody @RestControllerAdvice |
- 内容协商与格式
| 注解 | 功能描述 |
|---|---|
| @CrossOrigin | 跨域支持配置CORS策略 @CrossOrigin(origins = “*”, maxAge = 3600) |
| @Valid / @Validated | 参数校验配合JSR-303校验注解 public User create(@Valid @RequestBody User user) |
| @Date | TimeFormat 日期格式转换 @DateTimeFormat(pattern = “yyyy-MM-dd”) Date birthDate |
- 高级特性
| 注解 | 功能描述 |
|---|---|
| @InitBinder | 自定义数据绑定注册属性编辑器 @InitBinder public void initBinder(WebDataBinder binder) |
| @MatrixVariable | 处理矩阵变量 /users;department=IT |
| @MatrixVariable | String department |
| @RequestScope | 请求作用域的Bean @Component @RequestScope public class UserSession |
七、平时开发过程中遇到的异常
- Spring核心异常
- NoSuchBeanDefinitionException
原因:依赖注入时找不到Bean
场景:
@Autowired
private NonExistentService service;
解决:
检查Bean是否被@Component/@Service标注
确保包扫描路径包含该Bean
检查是否存在多个同类型Bean导致冲突
- BeanCreationException
原因:Bean初始化失败
典型子类:
UnsatisfiedDependencyException:依赖不满足
BeanInstantiationException:构造函数执行失败
解决:检查构造函数和依赖注入
- Spring MVC异常
- HttpMessageNotReadableException
原因:请求体解析失败(JSON格式错误/类型不匹配)
示例:
{"age": "abc"} // 期望int但收到字符串
解决:
前端校验数据格式
添加@ExceptionHandler处理异常
- MethodArgumentNotValidException
原因:@Valid参数校验失败
示例:public ResponseEntity<?> createUser(@Valid @RequestBody User user)
解决:
@ExceptionHandler
public ErrorResponse handleValidationException(MethodArgumentNotValidException ex) {
return ex.getBindingResult().getAllErrors().stream()
.map(error -> new ErrorResponse(error.getDefaultMessage()))
.collect(Collectors.toList());
}
- MissingServletRequestParameterException
原因:缺少必需的请求参数
示例:@GetMapping public User get(@RequestParam String id) {...}
调用:GET /user (缺少id参数)
- 数据库相关异常
- DataAccessException (Spring抽象异常)
子类:
DuplicateKeyException:唯一键冲突
DataIntegrityViolationException:数据完整性违规
DeadlockLoserDataAccessException:数据库死锁
JpaSystemException (JPA)
常见原因:
延迟加载(Lazy Loading)时Session已关闭
实体状态管理错误
- 空指针异常
- NullPointerException
高频场景:
// 1. 对象方法调用
user.getName().toUpperCase(); // user或name为null
// 2. 集合操作
list.get(0).doSomething(); // list为空
// 3. 自动拆箱
int id = Integer.parseInt(idStr); // idStr为null
防御方案:
// 使用Optional
Optional.ofNullable(user)
.map(User::getName)
.map(String::toUpperCase)
.orElse("UNKNOWN");
// 使用Objects.requireNonNull
public User updateUser(User user) {
Objects.requireNonNull(user, "User不能为空");
// ...
}
五、IO与资源异常
- FileNotFoundException
new FileInputStream("non_existent.txt");
IOException
网络超时、文件读写中断等
- 并发异常
- ConcurrentModificationException
场景:遍历集合时修改集合
List<String> list = new ArrayList<>(List.of("A", "B"));
for (String s : list) {
list.remove(s); // 抛出异常
}
解决:使用迭代器的remove()方法
- RejectedExecutionException
原因:线程池拒绝新任务(队列满或已关闭)
- 类型转换异常
ClassCastException
Object obj = "Hello";
Integer num = (Integer) obj; // 抛出异常
//NumberFormatException
int num = Integer.parseInt("123abc"); // 数字格式错误
- 安全相关异常
-
AccessDeniedException
Spring Security权限不足 -
AuthenticationException
认证失败(用户名/密码错误)
八、Maven的Release版本和Snapshot版本有什么区别?我使用Release1.0版本,修改代码,进行打包,push到仓库中,不修改版本号,别人进行拉取得到最新的代码吗?
1.核心区别:发布机制与版本稳定性
-
Release版本(稳定版)
-
不可变性:同一版本号(如 1.0)的Release包发布到Maven仓库后,不允许覆盖。若代码修改后需重新发布,必须升级版本号(如 1.0 → 1.1)。
-
缓存策略:项目依赖Release版本时,Maven仅下载一次并缓存到本地。即使远程仓库有同名版本的新内容(理论上不应存在),本地也不会自动更新。
-
-
Snapshot版本(快照版)
- 可覆盖性:同一快照版本(如 1.0-SNAPSHOT)可多次发布到仓库,新包会覆盖旧包(或按时间戳保留多个副本)。
- 动态更新:依赖Snapshot版本的项目,每次构建时会主动检查远程仓库是否有更新,并自动下载最新版本。
2.Release版本无法不升级版本号更新
场景:你将Release 1.0 的代码修改后,不升级版本号直接打包推送至仓库。
结果:
仓库拒绝覆盖:Maven仓库(如Nexus)默认禁止覆盖Release包,推送操作会失败。
他人无法获取更新:即使强制覆盖成功(需仓库特殊配置),他人依赖 1.0 时因其本地已有缓存,不会主动拉取新内容。
✅ 结论:Release版本必须升级版本号才能更新代码并让他人获取到最新内容。
3.总结:关键差异速查表
| 特性 | Release版本 | Snapshot版本 |
|---|---|---|
| 版本号示例 | 1.0.0 | 1.0.0-SNAPSHOT |
| 是否允许覆盖 | ❌ 禁止覆盖 | ✅ 允许覆盖 |
| 本地缓存策略 | 不自动更新 | 每次构建检查远程更新 |
| 适用阶段 | 生产环境 | 开发/测试阶段 |
| 版本升级要求 | 必须修改版本号 | 无需修改版本号 |

被折叠的 条评论
为什么被折叠?



