北京中冶赛迪面试总结

时间:2024-06-01 18:37:46

自我介绍+项目介绍

目录

1.介绍一下mysql中的索引?

2.在 MySQL 中,如果您有一个 (a, b, c) 的联合索引,查询条件仅包含 a 和 c 而没有 b索引的生效情况?

3.mysql 锁的类型?

4.表中的数据重复删除怎么实现?

方法1:使用子查询

方法2:使用JOIN

方法3:使用ROW_NUMBER()

5.HashMap的遍历方式?

6.null 在mysql中用什么处理怎么存储?

7.什么情况下会垃圾回收?

8.创建线程的方式?

9.redis 有原子性 和 隔离性吗?

原子性(Atomicity)

隔离性(Isolation)

持久性(Durability)

总结

10.redis常用五大类型?

11.redis 键的淘汰策略?

12.SpringBoot核心注解有哪些?

13.Integer a = 321;  Integer b = 321; 用==比较会返回什么?

14.分库分表的键 怎么设置?

15.介绍一下Aop?

16.订单状态值很多,流转时用if else不太方便,有什么解决方案?


1.介绍一下mysql中的索引?

索引是数据库表中一列或多列的值存储的数据结构,通常是 B+ 树(B-Tree 的变种),它允许快速查找表中的行。

  • 主键索引:唯一且不允许空值,每个表只能有一个主键索引。
  • 唯一索引:唯一但允许有空值,可以有多个。
  • 普通索引:没有任何唯一性或非空限制。
  • 全文索引:用于对文本内容进行全文搜索。
  • 空间索引:用于地理空间数据类型,以优化空间查询。
  • 合适的列:为经常用于搜索条件、连接和排序的列创建索引。
  • 索引覆盖:如果一个查询只需要访问索引中的列,那么这个索引被称为覆盖索引,它可以直接用于查询结果,而不需要访问表数据。
  • 复合索引:当经常需要根据多个列进行搜索时,可以考虑创建复合索引。

       应该尽量避免违反最左前缀法则,已经其他使索引失效的场景,用慢查询日志以及explain可以分析慢sql,作出进一步优化。

2.在 MySQL 中,如果您有一个 (a, b, c) 的联合索引,查询条件仅包含 ac 而没有 b索引的生效情况?

  1. 索引最左前缀规则:MySQL 联合索引遵循最左前缀规则,这意味着查询时必须包含索引最左边的列才能使用索引。所以,如果您的查询包含了 a,索引就可以被使用。

  2. 索引选择性:如果 a 的选择性很高(不同的值很多),即使没有包含 b,查询也可能因为 a 的选择性而受益。

3.mysql 锁的类型?

1. 共享锁(Shared Locks)

共享锁,也称为读锁,允许多个事务同时读取同一资源,但不能修改它。当一个事务对数据加上共享锁后,其他事务可以继续加共享锁进行读取,但不能加排它锁进行写入。

2. 排它锁(Exclusive Locks)

排它锁,也称为写锁,允许事务修改数据。当一个事务对数据加上排它锁后,其他事务不能对该数据加任何类型的锁。

3. 行级锁(Row-level Locks)

MySQL 支持行级锁定,这是最细粒度的锁,只锁定相关的行记录。InnoDB 存储引擎使用行级锁来实现事务的隔离性。

4. 表级锁(Table-level Locks)

表级锁锁定整个表,MyISAM 和其他一些存储引擎使用表级锁。由于锁定了整个表,所以并发性能不如行级锁。

5. 间隙锁(Gap Locks)

间隙锁是一种行级锁,它锁定一个范围内的间隙,但不包括记录本身。间隙锁用于防止其他事务插入间隙中的新记录,以维护事务的可重复读性。

6. 临键锁(Next-Key Locks)

临键锁是 InnoDB 存储引擎中的一种锁,它结合了行锁和间隙锁。临键锁锁定一个记录以及记录前面的间隙。

7. 意向锁(Intention Locks)

意向锁是一种表明事务想要在更细粒度上加锁的锁。它们用于在锁定层次结构中向上升级,从行级到表级。意向锁分为意向共享锁(Intention Shared Lock)和意向排它锁(Intention Exclusive Lock)。

8. 自增锁(AUTO-INC Locks)

在 InnoDB 中,自增锁用于管理表的自增字段。当插入新记录时,InnoDB 会请求一个自增锁,以确保自增字段的值是唯一的。

9. 全局锁(Global Locks)

全局锁是 MySQL 中最高层次的锁,它锁定整个数据库系统。这种锁通常用于复制和恢复操作。

10. 元数据锁(Metadata Locks)

元数据锁用于控制对数据库结构的更改,如添加或删除表。

11. 死锁(Deadlocks)

死锁发生在两个或多个事务相互等待对方持有的锁,导致无法继续执行。

4.表中的数据重复删除怎么实现?

方法1:使用子查询

假设你有一个表my_table,其中包含字段id(假设是主键)和column1,你想删除column1字段的重复数据,保留每个重复组的第一个记录。

DELETE FROM my_table
WHERE id NOT IN (
    SELECT MIN(id)
    FROM my_table
    GROUP BY column1
);

这个查询首先找出每个column1值的最小id,然后删除不在这些最小id列表中的所有记录。

方法2:使用JOIN

使用LEFT JOIN来保留重复数据中的第一条记录。

DELETE my_table
FROM my_table
LEFT JOIN (
    SELECT MIN(id) as min_id
    FROM my_table
    GROUP BY column1
) AS subquery
ON my_table.id = subquery.min_id
WHERE my_table.id > subquery.min_id;

这个查询将my_table与一个子查询进行左连接,子查询返回每个column1值的最小id。然后,它删除连接后my_tableid大于最小id的所有记录。

方法3:使用ROW_NUMBER()

如果你使用的数据库支持窗口函数(如SQL Server、PostgreSQL、MySQL 8.0+等),可以使用ROW_NUMBER()来为每个重复组分配一个序号,然后删除序号大于1的记录。

DELETE FROM my_table
WHERE row_num > 1;y.min_id;

首先,你需要创建一个临时表或使用WITH语句来确定每个记录的序号:

WITH RankedRecords AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY column1 ORDER BY id) AS row_num
    FROM my_table
)
DELETE FROM RankedRecords
WHERE row_num > 1;

在这个例子中,ROW_NUMBER()为每个column1值的分组分配一个序号,序号从1开始,并且按id排序。然后,删除序号大于1的所有记录。

5.HashMap的遍历方式?

HashMap 是 Java 中实现的一个基于哈希表的 Map 接口,它存储键值对(key-value pairs)。在 Java 中,有几种不同的方式可以遍历 HashMap

1. 使用 keySet() 方法

这是最常用的遍历 HashMap 的方法。首先获取所有键的集合,然后遍历这个集合,并使用每个键来获取对应的值。

HashMap<KeyType, ValueType> map = new HashMap<>();
// 假设map已经初始化并填充了数据

for (KeyType key : map.keySet()) {
    ValueType value = map.get(key);
    // 处理键和值
}

2. 使用 entrySet() 方法

entrySet() 方法返回的是一个 Set 集合,集合中的元素是 Map.Entry 对象。每个 Map.Entry 对象都包含一个键和一个值。

for (Map.Entry<KeyType, ValueType> entry : map.entrySet()) {
    KeyType key = entry.getKey();
    ValueType value = entry.getValue();
    // 处理键和值
}

3. 使用 Java 8 的 forEach() 方法

从 Java 8 开始,Map 接口提供了 forEach() 方法,它接受一个 BiConsumer 函数式接口作为参数,可以对每个键值对执行操作。

map.forEach((key, value) -> {
    // 处理键和值
});

4. 使用 values() 方法

values() 方法返回的是一个 Collection,包含 HashMap 中所有的值。通常,这种方法不推荐使用,因为如果你需要同时访问键和值,它就不如 keySet()entrySet() 方法高效。

Collection<ValueType> values = map.values();
for (ValueType value : values) {
    // 仅处理值,如果需要键,需要再次查询
}

5. 使用 Java 8 的 Stream API

从 Java 8 开始,可以使用 stream() 方法来获取 HashMap 的流,并进行更复杂的操作。

map.entrySet().stream()
    .forEach(entry -> {
        KeyType key = entry.getKey();
        ValueType value = entry.getValue();
        // 处理键和值
    });

6. 使用迭代器(Iterator)

可以使用 iterator() 方法获取迭代器,然后使用迭代器遍历 HashMap

Iterator<Map.Entry<KeyType, ValueType>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<KeyType, ValueType> entry = iterator.next();
    KeyType key = entry.getKey();
    ValueType value = entry.getValue();
    // 处理键和值
}

每种遍历方式都有其适用场景,选择哪一种取决于你的具体需求。例如,如果你需要修改 HashMap,那么使用迭代器可能是更好的选择。如果你只是需要读取数据,那么 keySet()entrySet()forEach() 方法可能更简单、更直观。

6.null 在mysql中用什么处理怎么存储?

在 MySQL 中,NULL 不是一个数据类型,而是一个特殊的标记,用来表示字段中没有数据。在 SQL 中,NULL 用于表示未知或缺失的数据值。

每个数据类型都可以存储 NULL 值,例如:

  • INT:整数类型,可以存储 NULL 值。
  • VARCHAR:可变长度的字符串类型,可以存储 NULL 值。
  • DATE:日期类型,可以存储 NULL 值。
  • FLOAT:浮点数类型,可以存储 NULL 值。
  • 等等...

NULL 值在 SQL 中的处理与其他值不同,例如:

  • NULL 与任何值(包括另一个 NULL)的比较总是产生 NULL 结果,这意味着比较是不确定的。
  • 在使用 COUNT() 函数时,默认情况下 NULL 值不会被计入总数。
  • 在使用 ORDER BY 子句时,NULL 值通常会被排序在结果集的开始或结束位置,这取决于具体的 SQL 配置和版本。

因此,设计数据库时,需要根据数据模型和业务需求来决定哪些字段可以存储 NULL 值,以及如何处理这些 NULL 值。

7.什么情况下会垃圾回收?

垃圾回收(Garbage Collection, GC)通常在以下情况下触发:

  1. 内存分配请求时

    • 当应用程序尝试分配新对象,而内存不足时,垃圾回收器会被触发以释放内存。
  2. 内存使用达到阈值

    • 在一些垃圾回收系统中,内存使用达到特定阈值时会触发垃圾回收。
  3. 定时触发

    • 有些垃圾回收器会根据设定的时间间隔定期执行,以清理内存。
  4. 系统空闲时

    • 在一些系统中,垃圾回收可能在系统空闲时自动执行,以优化内存使用。
  5. 显式调用

    • 在某些编程语言中,开发者可以通过特定的方法或函数显式请求执行垃圾回收。
  6. 内存碎片整理

    • 当内存分配和回收导致内存碎片化时,垃圾回收器可能会被触发以整理内存。
  7. 新生代到老年代的晋升

    • 在使用分代垃圾回收策略的系统中,对象在新生代存活一定次数后会被晋升到老年代,这可能会触发老年代的垃圾回收。
  8. 堆空间不足

    • 当整个堆空间不足时,垃圾回收器会被触发以尝试释放更多内存。
  9. 外部触发

    • 某些外部事件或条件(如操作系统的内存压力)也可能触发垃圾回收。
  10. 特定事件

    • 在一些系统中,特定的事件(如类加载器卸载)可能会触发垃圾回收。

8.创建线程的方式?

1. 继承 Thread 类

2. 实现 Runnable 接口

3. 使用 Callable 和 Future

4. 使用 Executors 框架

9.redis 有原子性 和 隔离性吗?

原子性(Atomicity)

在 Redis 中,单个命令的执行是原子性的。这意味着当一个命令被执行时,它要么完全执行,要么完全不执行,不会出现执行到一半的情况。Redis 通过单线程来处理命令,确保了命令执行的原子性。

然而,如果一个操作涉及多个命令,Redis 默认情况下并不保证这些命令作为一个整体的原子性。为了实现多个命令的原子性,Redis 提供了以下两种机制:

  1. 事务(Transactions):通过 MULTIEXECWATCH 和 DISCARD 命令,Redis 允许客户端将多个命令打包在一起执行,要么全部成功,要么全部失败。
  2. Lua 脚本(Lua scripting):Redis 支持在服务器端执行 Lua 脚本,脚本中的命令会作为一个整体执行,保证原子性。

隔离性(Isolation)

Redis 默认运行在单线程模式下,这意味着在任何给定时间点,只有一个命令被执行。因此,Redis 自然地提供了一定程度的隔离性,因为不会有多个命令同时干扰彼此的执行。

但是,Redis 的隔离性与传统数据库的隔离级别(如读已提交、可重复读、可串行化)不同。在 Redis 中:

  • 无锁(Lock-free):Redis 的操作不需要使用锁,因为单线程模型保证了不会有并发冲突。
  • 并发控制:虽然 Redis 单线程模型提供了隔离性,但在高并发环境下,客户端可能会遇到竞态条件。Redis 提供了一些机制来控制并发,如使用 WATCH 命令监视键值的变化,以及使用事务来保证操作的一致性。

持久性(Durability)

Redis 还提供了持久性选项,通过 RDB 快照和 AOF 日志记录,确保数据在系统故障后能够恢复。

总结

Redis 的原子性和隔离性与传统的关系型数据库不同,它通过单线程模型和事务机制来保证操作的原子性,并通过单线程执行来自然地提供隔离性。然而,Redis 不提供传统数据库中的严格隔离级别,所以在设计系统时需要考虑到这一点,并根据需要使用 Redis 提供的机制来保证数据的一致性和完整性。

10.redis常用五大类型?

  1. 字符串(Strings)

    • 基本的数据类型,用于存储简单的字符串数据。
    • 可以执行的操作包括设置(SET)、获取(GET)、删除(DEL)等。
  2. 列表(Lists)

    • 列表是简单的字符串列表,按照插入顺序排序。
    • 可以执行的操作包括左推(LPUSH/RPUSH)、右弹(LPOP/RPOP)、获取列表长度(LLEN)等。
  3. 集合(Sets)

    • 集合是一个无序集合,可以存储不重复的字符串元素。
    • 可以执行的操作包括添加(SADD)、移除(SREM)、检查成员(SISMEMBER)等。
  4. 有序集合(Sorted Sets)

    • 类似于集合,但是每个元素都有一个分数(score)与之关联,并且按照分数进行排序。
    • 可以执行的操作包括添加(ZADD)、获取排名(ZRANK/ZREVRANK)、获取范围(ZRANGE/ZREVRANGE)等。
  5. 哈希(Hashes)

    • 哈希是一个键值对集合,其中每个键和值都是字符串。
    • 可以执行的操作包括设置字段(HSET)、获取字段(HGET)、删除字段(HDEL)等。

11.redis 键的淘汰策略?

  1. noeviction:这是默认的淘汰策略。当内存达到限制时,Redis 将拒绝所有会修改数据集的命令(例如 SET、LPUSH 等),但不会自动删除任何键。

  2. allkeys-lru:在所有键中,根据最近最少使用(Least Recently Used)算法淘汰数据。

  3. volatile-lru:在设置了过期时间的键中,根据 LRU 算法淘汰数据。

  4. allkeys-random:在所有键中随机选择一个键进行淘汰。

  5. volatile-random:在设置了过期时间的键中随机选择一个键进行淘汰。

  6. volatile-ttl:在设置了过期时间的键中,选择即将过期的键进行淘汰。

12.SpringBoot核心注解有哪些?

  1. @SpringBootApplication

    • 组合注解,包含 @Configuration@EnableAutoConfiguration 和 @ComponentScan
    • 用于定义主应用程序类,指示 Spring Boot 应运行类路径扫描。
  2. @RestController

    • 组合注解,包含 @Controller 和 @ResponseBody
    • 用于定义 RESTful web 服务。
  3. @RequestMapping

    • 用于映射 HTTP 请求到控制器的处理方法。
    • 可以指定请求的路径、方法等。
  4. @GetMapping@PostMapping@PutMapping@DeleteMapping

    • 特定类型的 @RequestMapping,分别用于处理 GET、POST、PUT 和 DELETE 请求。
  5. @Autowired

    • 用于自动装配依赖注入的组件。
  6. @Service

    • 用于标记服务层的组件。
  7. @Repository

    • 用于标记数据访问层的组件,通常与数据库交互。
  8. @Component

    • 用于标记 Spring 管理的组件。
  9. @Configuration

    • 用于标记类包含 bean 的定义。
  10. @Bean

    • 用于在方法级别上声明一个 bean。
  11. @Value

    • 用于注入外部配置的值。
  12. @PropertySource

    • 用于加载属性文件。
  13. @EnableAutoConfiguration

    • 告诉 Spring Boot 根据添加的 jar 依赖自动配置项目。
  14. @ComponentScan

    • 用于指定 Spring 搜索组件、配置、服务等的包路径。
  15. @RestControllerAdvice

    • 用于定义全局异常处理、数据绑定或数据验证。
  16. @PathVariable

    • 用于将 URL 中的模板变量绑定到控制器处理方法的参数上。
  17. @RequestParam

    • 用于将请求参数映射到控制器处理方法的参数上。
  18. @RequestBody

    • 用于将 HTTP 请求的 body 绑定到控制器处理方法的参数上。
  19. @ResponseBody

    • 用于指示方法的返回值应该被作为 HTTP 响应的正文返回。
  20. @Profile

    • 用于指定组件的激活环境。
  21. @Async

    • 用于声明异步方法。
  22. @Scheduled

    • 用于声明定时任务。

13.Integer a = 321;  Integer b = 321; 用==比较会返回什么?

在 Java 中,Integer 类型的对象进行 == 比较时,比较的是它们在内存中的引用地址,而不是它们所封装的值。如果两个 Integer 对象是通过自动装箱(autoboxing)创建的,它们通常指向内存中的同一个缓存对象。Java 为每个整数值 -128 到 127 缓存了一个 Integer 对象,所以当你创建一个这个范围内的 Integer 对象时,实际上是从一个缓存中取得的。超过范围会返回false。

14.分库分表的键 怎么设置?

分库键(Shard Key)

  1. 业务无关性:选择与业务逻辑无关的字段作为分库键,以避免数据倾斜。

  2. 数据均匀性:选择能够使数据均匀分布的字段,例如用户ID、订单ID等。

  3. 查询模式:根据查询模式选择分库键,使得查询操作能够快速定位到特定的库。

  4. 范围分区:如果数据具有时间属性,可以选择时间字段作为分库键,实现时间范围分区。

分表键(Partition Key)

  1. 数据量预估:根据数据增长趋势和表的大小预估,选择合适的字段进行分表。

  2. 访问模式:根据数据的访问模式,选择能够提高查询效率的字段作为分表键。

  3. 数据关联性:如果表之间存在关联关系,可以选择关联字段作为分表键,以保持数据的一致性。

  4. 复合分区:可以使用多个字段的组合作为分表键,实现复合分区策略。

常见分库分表策略

  1. 哈希分区:使用哈希函数对分库/分表键进行哈希计算,根据哈希值将数据分配到不同的库或表。

  2. 范围分区:根据分库/分表键的数值范围进行分区,例如按年份、按ID范围等。

  3. 列表分区:将分库/分表键的值映射到一个预定义的列表中,根据列表中的顺序分配数据。

  4. 一致性哈希:使用一致性哈希算法分配数据,可以在增减节点时减少数据迁移。

15.介绍一下Aop?

AOP 的核心概念包括:

  1. 切面(Aspect):切面是一组横切关注点的模块化表示,它包括通知(Advice)、切点(Pointcut)、目标对象(Target Object)、代理(Proxy)等。

  2. 通知(Advice):通知是切面的一部分,它定义了何时以及如何增强目标对象的方法执行。通知可以在方法的执行前后、方法抛出异常时或在方法正常执行完成后执行。

  3. 切点(Pointcut):切点定义了一组特定的连接点(Join Point),这些连接点是程序执行过程中的特定位置,如方法的调用或异常的处理。

  4. 连接点(Join Point):连接点是程序执行过程中可以插入切面的特定位置,通常是方法的调用或处理程序的执行。

  5. 目标对象(Target Object):目标对象是被增强的对象,通常是应用程序中的业务逻辑类。

  6. 代理(Proxy):代理是目标对象的一个替代品,它在不修改目标对象的情况下,通过拦截方法调用来实现增强。

16.订单状态值很多,流转时用if else不太方便,有什么解决方案?

  1. 策略模式(Strategy Pattern)

    • 策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以互换。
    • 为订单的每个状态实现一个策略接口,订单对象根据当前状态使用相应的策略。
  2. 工厂模式(Factory Pattern)

    • 工厂模式用于创建对象,而不需要指定将要创建的具体类。
    • 为每个订单状态创建一个具体的工厂类,订单对象通过工厂来获取并执行状态相关的操作。