一些基本概念介绍
- https://www.binance.com/en/square/post/391229
面试问题
- Mysql 分库分表
-
高并发、数据量大
-
一般我们认为,单表行数超过 500 万行或者单表容量超过 2GB之后,才需要考虑做分库分表了,小于这个数据量,遇到性能问题先建议大家通过其他优化来解决。
-
横向拆分(水平拆分)和纵向拆分(垂直拆分)。假如我们有一张表,如果把这张表中某一条记录的多个字段,拆分到多张表中,这种就是纵向拆分。那如果把一张表中的不同的记录分别放到不同的表中,这种就是横向拆分。
-
分表算法
- 直接取模
- Hash取模
- 一致性Hash
-
全局ID
- UUID
- 多个单表+步长做自增主键
- 雪花算法
-
分库分表框架
- B+树
- N叉排序树
- 数据库和操作系统的文件系统
- B+树的定义
B+树
是应文件系统所需而出的一种B-树
的变型树。一棵m阶
的B+树
和m阶的B-树
的差异在于:
1) 有n棵子树的节点中含有n个关键字(即每个关键字对应一棵子树);
2) 所有叶子节点中包含了全部关键字的信息, 及指向含这些关键字记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接;
3) 所有的非终端节点可以看成是索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字
4) 除根节点外,其他所有节点中所含关键字的个数必须>=⌈m/2⌉
(注意: B-树
是除根以外的所有非终端节点至少有⌈m/2⌉
棵子树)
下图是所示为一棵3阶的B+树
,通常在B+树
上有两个指针头, 一个指向根节点,另一个指向关键字最小的叶子节点。因此,可以对B+树
进行两种查找运算: 一种是从最小关键字起顺序查找,另一种是从根节点开始,进行随机查找。
-
与B树的比较
- 所有的叶子节点中包含了全部关键字的信息,即指向含有这些关键字记录的指针,且叶子节点本身依关键字的大小
自小而大
的顺序链接。(而B-树
的叶子节点并没有包括全部需要查找的信息) - 所有的非终端节点可以看成是索引部分,节点中仅含有其子树根节点中最大(或最小)关键字。(而
B-树
的非终节点也包含需要查找的有效信息)
- 所有的叶子节点中包含了全部关键字的信息,即指向含有这些关键字记录的指针,且叶子节点本身依关键字的大小
-
B+树用途-主要适用于索引操作
B+树
的磁盘读写代价更低- B+树的查询效率更加稳定: 由于非终节点并不是最终指向文件内容的节点,而只是叶子节点中关键字的索引。所以任何关键字的查找必须走一条从根节点到叶子节点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
-
MySQL中的B+树
MyISAM
和InnoDB
两种引擎MyISAM
中有两种索引,分别是主索引和辅助索引.MyISAM分别会存在一个索引文件和数据文件,它的主索引是非聚集索引。当我们查询的时候,我们找到叶子节点中保存的地址,然后通过地址我们找到对应的信息。- 在InnoDB存储引擎中,表数据文件本身就是按
B+树
组织的一个索引结构,这棵树的叶节点数据保存了完整的数据记录,所以我们把它的主索引叫做聚集索引 - 对聚簇索引的解释是: 聚簇索引的顺序就是数据的物理存储顺序; 而对非聚簇索引的解释是: 索引顺序与数据物理排列无关。正是因为如此,所以一个表最多只能有一个聚簇索引。直观上来说,聚簇索引的叶子节点就是数据节点; 而非聚簇索引的叶子节点仍然是索引节点,只不过是指向对应数据块的指针。
- Mysql clustered 和 index
属性 | 聚簇索引(Clustered Index) | 普通索引(Secondary Index) |
---|---|---|
存储方式 | 数据行存储在索引中,索引叶子节点包含完整数据行 | 索引叶子节点存储指向数据行的指针 |
数量 | 每个表只有一个 | 一个表可以有多个普通索引 |
性能 | 读取性能高,特别是范围查询 | 查询时需要回表,性能稍低 |
物理存储顺序 | 数据行按照聚簇索引顺序存储 | 数据行的物理存储顺序与普通索引无关 |
维护成本 | 插入或更新可能导致页分裂 | 插入、更新时不会改变数据行的存储顺序 |
- Redis的删除策略,过期策略。
-
Redis 的过期策略是为了确保键在需要时可以自动失效,主要通过惰性删除和定期删除来实现。
-
惰性删除(Lazy Deletion)
-
原理:当一个键被访问时,Redis 会检查该键是否已经过期。如果过期,立即删除该键。
定期删除(Periodic Deletion)
-
原理:Redis 会每隔一段时间(默认 100 毫秒)随机抽取一部分设置了过期时间的键,检查并删除过期的键。
-
-
-
Redis 的删除策略是为了应对内存不足的场景,通过配置不同的回收策略来平衡性能、数据完整性和内存使用。
- noeviction(默认策略) 行为:当内存不足时,拒绝写入操作,直接返回错误。
- **allkeys-lru(最常用)**行为:在所有键中,移除最近最少使用的键(LRU)。
- 求无序数组 第K 大 (讲了partion算法,说的是O(N),但是面试官觉得是O(NlogN),当时给他证明复杂度没描述太明白)
import java.math.BigInteger;
class Solution {
public String kthLargestNumber(String[] nums, int k) {
Arrays.sort(nums, (a, b) -> {
BigInteger numA = new BigInteger(a);
BigInteger numB = new BigInteger(b);
return numB.compareTo(numA);
});
return nums[k-1];
}
}
class Solution {
public int findKthLargest(int[] nums, int k) {
return quickSort(nums,nums.length - k,0,nums.length-1);
}
private int quickSort(int[] A,int k,int left,int right){
int low=left,high=right;
int pivot = A[low];
while (low < high) {
while (low < high && A[high] >= pivot) {
high--;
}
A[low] = A[high]; // 比枢轴元素小的元素移动到左端
while (low < high && A[low] <= pivot) {
low++;
}
A[high] = A[low]; // 比枢轴元素大的元素移动到右端
}
A[low] = pivot; // 枢轴元素放到最终位置
if (low==k) return A[low];
else if(low<k){
return quickSort(A,k, low+1, right);
} else {
return quickSort(A,k, left, low-1);
}
}
}
- 输出二叉树最后一层的元素
public List<Integer> getLastLevelElements(TreeNode root) {
// 使用队列进行层序遍历
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
List<Integer> result = new ArrayList<>();
while (!queue.isEmpty()) {
int levelSize = queue.size(); // 当前层的节点个数
List<Integer> currentLevel = new ArrayList<>();
// 遍历当前层
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val); // 将当前节点值添加到当前层的结果中
// 将子节点加入队列
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
// 当前层遍历结束后,更新 result 为当前层
result = currentLevel;
}
return result; // 返回最后一层的元素
}
- 求两个数组的重复部分
public int[] findDuplicates(int[] nums1, int[] nums2) {
// 使用 HashSet 存储 nums1 的元素
Set<Integer> set1 = new HashSet<>();
for (int num : nums1) {
set1.add(num);
}
// 使用 HashSet 存储重复的元素
Set<Integer> resultSet = new HashSet<>();
// 遍历 nums2,检查重复的元素
for (int num : nums2) {
if (set1.contains(num)) {
resultSet.add(num);
}
}
// 将结果转化为 int[] 数组
int[] result = new int[resultSet.size()];
int index = 0;
for (int num : resultSet) {
result[index++] = num;
}
return result;
}