自我介绍+项目介绍
目录
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)
的联合索引,查询条件仅包含 a
和 c
而没有 b索引的生效情况?
-
索引最左前缀规则:MySQL 联合索引遵循最左前缀规则,这意味着查询时必须包含索引最左边的列才能使用索引。所以,如果您的查询包含了
a
,索引就可以被使用。 -
索引选择性:如果
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_table
中id
大于最小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)通常在以下情况下触发:
-
内存分配请求时:
- 当应用程序尝试分配新对象,而内存不足时,垃圾回收器会被触发以释放内存。
-
内存使用达到阈值:
- 在一些垃圾回收系统中,内存使用达到特定阈值时会触发垃圾回收。
-
定时触发:
- 有些垃圾回收器会根据设定的时间间隔定期执行,以清理内存。
-
系统空闲时:
- 在一些系统中,垃圾回收可能在系统空闲时自动执行,以优化内存使用。
-
显式调用:
- 在某些编程语言中,开发者可以通过特定的方法或函数显式请求执行垃圾回收。
-
内存碎片整理:
- 当内存分配和回收导致内存碎片化时,垃圾回收器可能会被触发以整理内存。
-
新生代到老年代的晋升:
- 在使用分代垃圾回收策略的系统中,对象在新生代存活一定次数后会被晋升到老年代,这可能会触发老年代的垃圾回收。
-
堆空间不足:
- 当整个堆空间不足时,垃圾回收器会被触发以尝试释放更多内存。
-
外部触发:
- 某些外部事件或条件(如操作系统的内存压力)也可能触发垃圾回收。
-
特定事件:
- 在一些系统中,特定的事件(如类加载器卸载)可能会触发垃圾回收。
8.创建线程的方式?
1. 继承 Thread
类
2. 实现 Runnable
接口
3. 使用 Callable
和 Future
4. 使用 Executors
框架
9.redis 有原子性 和 隔离性吗?
原子性(Atomicity)
在 Redis 中,单个命令的执行是原子性的。这意味着当一个命令被执行时,它要么完全执行,要么完全不执行,不会出现执行到一半的情况。Redis 通过单线程来处理命令,确保了命令执行的原子性。
然而,如果一个操作涉及多个命令,Redis 默认情况下并不保证这些命令作为一个整体的原子性。为了实现多个命令的原子性,Redis 提供了以下两种机制:
-
事务(Transactions):通过
MULTI
、EXEC
、WATCH
和DISCARD
命令,Redis 允许客户端将多个命令打包在一起执行,要么全部成功,要么全部失败。 - 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常用五大类型?
-
字符串(Strings):
- 基本的数据类型,用于存储简单的字符串数据。
- 可以执行的操作包括设置(SET)、获取(GET)、删除(DEL)等。
-
列表(Lists):
- 列表是简单的字符串列表,按照插入顺序排序。
- 可以执行的操作包括左推(LPUSH/RPUSH)、右弹(LPOP/RPOP)、获取列表长度(LLEN)等。
-
集合(Sets):
- 集合是一个无序集合,可以存储不重复的字符串元素。
- 可以执行的操作包括添加(SADD)、移除(SREM)、检查成员(SISMEMBER)等。
-
有序集合(Sorted Sets):
- 类似于集合,但是每个元素都有一个分数(score)与之关联,并且按照分数进行排序。
- 可以执行的操作包括添加(ZADD)、获取排名(ZRANK/ZREVRANK)、获取范围(ZRANGE/ZREVRANGE)等。
-
哈希(Hashes):
- 哈希是一个键值对集合,其中每个键和值都是字符串。
- 可以执行的操作包括设置字段(HSET)、获取字段(HGET)、删除字段(HDEL)等。
11.redis 键的淘汰策略?
-
noeviction:这是默认的淘汰策略。当内存达到限制时,Redis 将拒绝所有会修改数据集的命令(例如 SET、LPUSH 等),但不会自动删除任何键。
-
allkeys-lru:在所有键中,根据最近最少使用(Least Recently Used)算法淘汰数据。
-
volatile-lru:在设置了过期时间的键中,根据 LRU 算法淘汰数据。
-
allkeys-random:在所有键中随机选择一个键进行淘汰。
-
volatile-random:在设置了过期时间的键中随机选择一个键进行淘汰。
-
volatile-ttl:在设置了过期时间的键中,选择即将过期的键进行淘汰。
12.SpringBoot核心注解有哪些?
-
@SpringBootApplication:
- 组合注解,包含
@Configuration
、@EnableAutoConfiguration
和@ComponentScan
。 - 用于定义主应用程序类,指示 Spring Boot 应运行类路径扫描。
- 组合注解,包含
-
@RestController:
- 组合注解,包含
@Controller
和@ResponseBody
。 - 用于定义 RESTful web 服务。
- 组合注解,包含
-
@RequestMapping:
- 用于映射 HTTP 请求到控制器的处理方法。
- 可以指定请求的路径、方法等。
-
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping:
- 特定类型的
@RequestMapping
,分别用于处理 GET、POST、PUT 和 DELETE 请求。
- 特定类型的
-
@Autowired:
- 用于自动装配依赖注入的组件。
-
@Service:
- 用于标记服务层的组件。
-
@Repository:
- 用于标记数据访问层的组件,通常与数据库交互。
-
@Component:
- 用于标记 Spring 管理的组件。
-
@Configuration:
- 用于标记类包含 bean 的定义。
-
@Bean:
- 用于在方法级别上声明一个 bean。
-
@Value:
- 用于注入外部配置的值。
-
@PropertySource:
- 用于加载属性文件。
-
@EnableAutoConfiguration:
- 告诉 Spring Boot 根据添加的 jar 依赖自动配置项目。
-
@ComponentScan:
- 用于指定 Spring 搜索组件、配置、服务等的包路径。
-
@RestControllerAdvice:
- 用于定义全局异常处理、数据绑定或数据验证。
-
@PathVariable:
- 用于将 URL 中的模板变量绑定到控制器处理方法的参数上。
-
@RequestParam:
- 用于将请求参数映射到控制器处理方法的参数上。
-
@RequestBody:
- 用于将 HTTP 请求的 body 绑定到控制器处理方法的参数上。
-
@ResponseBody:
- 用于指示方法的返回值应该被作为 HTTP 响应的正文返回。
-
@Profile:
- 用于指定组件的激活环境。
-
@Async:
- 用于声明异步方法。
-
@Scheduled:
- 用于声明定时任务。
13.Integer a = 321; Integer b = 321; 用==比较会返回什么?
在 Java 中,Integer
类型的对象进行 ==
比较时,比较的是它们在内存中的引用地址,而不是它们所封装的值。如果两个 Integer
对象是通过自动装箱(autoboxing)创建的,它们通常指向内存中的同一个缓存对象。Java 为每个整数值 -128 到 127 缓存了一个 Integer
对象,所以当你创建一个这个范围内的 Integer
对象时,实际上是从一个缓存中取得的。超过范围会返回false。
14.分库分表的键 怎么设置?
分库键(Shard Key)
-
业务无关性:选择与业务逻辑无关的字段作为分库键,以避免数据倾斜。
-
数据均匀性:选择能够使数据均匀分布的字段,例如用户ID、订单ID等。
-
查询模式:根据查询模式选择分库键,使得查询操作能够快速定位到特定的库。
-
范围分区:如果数据具有时间属性,可以选择时间字段作为分库键,实现时间范围分区。
分表键(Partition Key)
-
数据量预估:根据数据增长趋势和表的大小预估,选择合适的字段进行分表。
-
访问模式:根据数据的访问模式,选择能够提高查询效率的字段作为分表键。
-
数据关联性:如果表之间存在关联关系,可以选择关联字段作为分表键,以保持数据的一致性。
-
复合分区:可以使用多个字段的组合作为分表键,实现复合分区策略。
常见分库分表策略
-
哈希分区:使用哈希函数对分库/分表键进行哈希计算,根据哈希值将数据分配到不同的库或表。
-
范围分区:根据分库/分表键的数值范围进行分区,例如按年份、按ID范围等。
-
列表分区:将分库/分表键的值映射到一个预定义的列表中,根据列表中的顺序分配数据。
-
一致性哈希:使用一致性哈希算法分配数据,可以在增减节点时减少数据迁移。
15.介绍一下Aop?
AOP 的核心概念包括:
-
切面(Aspect):切面是一组横切关注点的模块化表示,它包括通知(Advice)、切点(Pointcut)、目标对象(Target Object)、代理(Proxy)等。
-
通知(Advice):通知是切面的一部分,它定义了何时以及如何增强目标对象的方法执行。通知可以在方法的执行前后、方法抛出异常时或在方法正常执行完成后执行。
-
切点(Pointcut):切点定义了一组特定的连接点(Join Point),这些连接点是程序执行过程中的特定位置,如方法的调用或异常的处理。
-
连接点(Join Point):连接点是程序执行过程中可以插入切面的特定位置,通常是方法的调用或处理程序的执行。
-
目标对象(Target Object):目标对象是被增强的对象,通常是应用程序中的业务逻辑类。
-
代理(Proxy):代理是目标对象的一个替代品,它在不修改目标对象的情况下,通过拦截方法调用来实现增强。
16.订单状态值很多,流转时用if else不太方便,有什么解决方案?
-
策略模式(Strategy Pattern):
- 策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以互换。
- 为订单的每个状态实现一个策略接口,订单对象根据当前状态使用相应的策略。
-
工厂模式(Factory Pattern):
- 工厂模式用于创建对象,而不需要指定将要创建的具体类。
- 为每个订单状态创建一个具体的工厂类,订单对象通过工厂来获取并执行状态相关的操作。