1. 什么是存储过程,触发器
存储过程
- 定义:存储过程是一组为了完成特定功能而预先编译好的 SQL 语句集,它被存储在数据库中,用户可以通过指定存储过程的名称并传递相应参数(如果有)来执行它。存储过程可以包含逻辑判断、循环等流程控制语句,能有效减少网络通信量,提升执行效率。
- 要点:
- 封装性:将复杂的业务逻辑封装起来,提高代码的复用性。
- 性能:经过预编译和缓存,执行速度更快。
- 安全性:可以设置用户对存储过程的执行权限,增强数据安全性。
- 应用:在企业级应用中,常用于处理复杂的业务逻辑,如财务系统的报表生成、订单处理等。不同的数据库系统提供了各自的存储过程语言,例如 MySQL 的
DELIMITER
语法,SQL Server 的 T - SQL 等。 - 代码示例(MySQL):
sql
-- 创建一个存储过程,用于查询客户数量
DELIMITER //
CREATE PROCEDURE GetCustomerCount()
BEGINSELECT COUNT(*) FROM customers;
END //
DELIMITER ;-- 调用存储过程
CALL GetCustomerCount();
触发器
- 定义:触发器是一种特殊的存储过程,它会在某个表执行特定操作(如 INSERT、UPDATE、DELETE)之前或之后自动执行。触发器主要用于实现数据的完整性约束、日志记录等功能。
- 要点:
- 自动执行:无需手动调用,会在相应的表操作触发时自动执行。
- 关联表操作:与特定表的操作紧密绑定。
- 数据一致性:有助于保证数据的完整性和一致性。
- 应用:常用于审计追踪,比如记录用户对数据的修改操作;还可用于实现数据的级联更新和删除等。
- 代码示例(MySQL):
sql
-- 创建一个触发器,在插入新客户记录后,向日志表插入一条记录
DELIMITER //
CREATE TRIGGER after_insert_customer
AFTER INSERT ON customers
FOR EACH ROW
BEGININSERT INTO customer_log (action, customer_id) VALUES ('INSERT', NEW.customer_id);
END //
DELIMITER ;
2. B + 树和 B 树的区别 插入节点怎么分裂
区别
- 定义:
- B 树:是一种自平衡的多路搜索树,每个节点可以包含多个键和子节点,键和数据可以存储在所有节点中,查询操作可能在非叶子节点就结束。
- B + 树:是 B 树的一种变体,它的所有数据都存储在叶子节点,非叶子节点只存储索引信息,所有查询都必须到达叶子节点,并且叶子节点之间有指针相连,便于进行范围查询。
- 要点:
- B 树:键和数据存储在所有节点,查询可能提前结束。
- B + 树:数据仅在叶子节点,范围查询效率高。
- 应用:数据库索引大多采用 B + 树,因为它更适合范围查询和磁盘读写;B 树在文件系统等场景有一定的应用。
插入节点分裂
- B 树:当向一个节点插入一个键时,如果该节点的键数量超过了最大限制,节点就会分裂成两个节点。中间的键会被提升到父节点,左右两部分分别形成新的子节点。
- B + 树:插入时,如果叶子节点的键数量超过最大限制,叶子节点会分裂成两个节点,中间的键会被复制到父节点,同时调整叶子节点间的指针。
- 代码示例:由于 B 树和 B + 树的实现较为复杂,通常由数据库系统内部完成,以下是一个简单的 Java 示例来模拟 B 树节点分裂的基本逻辑:
java
import java.util.ArrayList;
import java.util.List;class BTreeNode {List<Integer> keys;List<BTreeNode> children;boolean isLeaf;public BTreeNode(boolean isLeaf) {this.isLeaf = isLeaf;keys = new ArrayList<>();children = new ArrayList<>();}
}public class BTreeSplitExample {private static final int MIN_DEGREE = 3;public static void splitChild(BTreeNode parent, int index, BTreeNode child) {BTreeNode newNode = new BTreeNode(child.isLeaf);parent.children.add(index + 1, newNode);parent.keys.add(index, child.keys.get(MIN_DEGREE - 1));for (int i = 0; i < MIN_DEGREE - 1; i++) {newNode.keys.add(child.keys.get(i + MIN_DEGREE));}child.keys.subList(MIN_DEGREE - 1, child.keys.size()).clear();if (!child.isLeaf) {for (int i = 0; i < MIN_DEGREE; i++) {newNode.children.add(child.children.get(i + MIN_DEGREE));}child.children.subList(MIN_DEGREE, child.children.size()).clear();}}
}
3. 有人建议给每张表都建一个自增主键, 这样做有什么优点跟缺点
优点
- 定义:自增主键是一个自动递增的整数,在每次插入新记录时,数据库会自动为其分配一个唯一的值。
- 要点:
- 唯一性:能够确保每行记录都有一个唯一的标识。
- 性能:索引效率较高,插入数据时无需额外计算主键值。
- 简化设计:无需考虑业务逻辑来生成唯一键,降低设计复杂度。
- 应用:在分布式系统中,可以结合数据库自增主键或使用分布式 ID 生成器。
- 代码示例(MySQL):
sql
-- 创建一个带有自增主键的表
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50),age INT
);-- 插入记录,无需指定主键值
INSERT INTO users (name, age) VALUES ('John', 25);
缺点
- 定义:自增主键依赖数据库自身的机制,可能存在一些局限性。
- 要点:
- 可预测性:容易被猜到记录的顺序和数量,存在一定的安全隐患。
- 数据迁移问题:不同数据库的自增主键机制可能不同,在数据迁移时可能会出现问题。
- 并发插入性能:在高并发情况下,自增主键可能成为性能瓶颈。
- 应用:对于一些对安全性要求较高的系统,不适合使用自增主键。
4. 如何理解 group by
- 定义:
GROUP BY
是 SQL 中的一个子句,用于将查询结果按照一个或多个列进行分组。分组之后,可以对每个组应用聚合函数(如 SUM、AVG、COUNT 等)进行统计计算。 - 要点:
- 分组依据:根据指定列的值将相同的记录分为一组。
- 聚合函数:对每个组进行统计计算。
- 筛选分组:可以结合
HAVING
子句对分组结果进行筛选。
- 应用:在数据分析、报表生成等场景中广泛应用,还可以结合
ORDER BY
对分组结果进行排序。 - 代码示例(SQL):
sql
-- 按照部门分组,统计每个部门的员工数量
SELECT department, COUNT(*) as employee_count
FROM employees
GROUP BY department;
5. mybatis 中 % 与 $ 的区别
- 定义:在 MyBatis 中,
#
和$
是两种不同的参数占位符。#
会将参数进行预编译处理,能有效防止 SQL 注入;$
会直接将参数值替换到 SQL 语句中。 - 要点:
#
:安全性高,适合传入普通参数。$
:存在 SQL 注入风险,但可用于动态传入表名、列名等。
- 应用:在实际开发中,应尽量使用
#
,只有在必要时才使用$
并做好参数验证。 - 代码示例(MyBatis XML):
xml
<select id="getUserById" parameterType="int" resultType="User">-- 使用 # 占位符,预编译处理SELECT * FROM users WHERE id = #{id}
</select><select id="getUserByColumnName" parameterType="String" resultType="User">-- 使用 $ 占位符,直接替换参数值SELECT * FROM users ORDER BY ${columnName}
</select>
6. 一个成绩表求出有不及格科目的同学
- 定义:假设成绩表
scores
包含student_id
和score
字段,通过查询找出成绩小于 60 分的学生。 - 要点:使用
WHERE
子句筛选成绩小于 60 分的记录,使用DISTINCT
去除重复的学生 ID。 - 应用:可结合其他表进行多表查询,如关联学生信息表获取学生的详细信息。
- 代码示例(SQL):
sql
-- 查询有不及格科目的学生 ID
SELECT DISTINCT student_id
FROM scores
WHERE score < 60;
7. 500 万数字排序, 内存只能容纳 5 万个, 如何排序, 如何优化?
排序方法
- 定义:采用外部排序算法,如归并排序。将 500 万数字分成 100 个块,每次读入 5 万个数字到内存中进行排序,然后将排序好的块写回磁盘。最后进行多路归并操作,将所有有序块合并成一个有序序列。
- 要点:
- 分块排序:将大数据分成小的数据块进行排序。
- 归并操作:将多个有序块合并成一个有序序列。
- 应用:可使用多线程并行处理每个块的排序,提高排序效率。
- 代码示例:以下是一个简单的 Java 示例,模拟外部排序的基本过程:
java
import java.io.*;
import java.util.*;public class ExternalSorting {private static final int CHUNK_SIZE = 50000;public static void externalSort(String inputFile, String outputFile) throws IOException {List<String> tempFiles = splitAndSort(inputFile);mergeFiles(tempFiles, outputFile);deleteTempFiles(tempFiles);}private static List<String> splitAndSort(String inputFile) throws IOException {List<String> tempFiles = new ArrayList<>();try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {List<Integer> chunk = new ArrayList<>();String line;while ((line = reader.readLine()) != null) {chunk.add(Integer.parseInt(line));if (chunk.size() == CHUNK_SIZE) {String tempFileName = createTempFile(chunk);tempFiles.add(tempFileName);chunk.clear();}}if (!chunk.isEmpty()) {String tempFileName = createTempFile(chunk);tempFiles.add(tempFileName);}}return tempFiles;}private static String createTempFile(List<Integer> chunk) throws IOException {Collections.sort(chunk);String tempFileName = UUID.randomUUID().toString();try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFileName))) {for (int num : chunk) {writer.write(String.valueOf(num));writer.newLine();}}return tempFileName;}private static void mergeFiles(List<String> tempFiles, String outputFile) throws IOException {PriorityQueue<FileEntry> pq = new PriorityQueue<>();List<BufferedReader> readers = new ArrayList<>();for (String tempFile : tempFiles) {BufferedReader reader = new BufferedReader(new FileReader(tempFile));String line = reader.readLine();if (line != null) {pq.add(new FileEntry(Integer.parseInt(line), reader));}readers.add(reader);}try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {while (!pq.isEmpty()) {FileEntry entry = pq.poll();writer.write(String.valueOf(entry.value));writer.newLine();String nextLine = entry.reader.readLine();if (nextLine != null) {pq.add(new FileEntry(Integer.parseInt(nextLine), entry.reader));}}}for (BufferedReader reader : readers) {reader.close();}}private static void deleteTempFiles(List<String> tempFiles) {for (String tempFile : tempFiles) {new File(tempFile).delete();}}static class FileEntry implements Comparable<FileEntry> {int value;BufferedReader reader;public FileEntry(int value, BufferedReader reader) {this.value = value;this.reader = reader;}@Overridepublic int compareTo(FileEntry other) {return Integer.compare(this.value, other.value);}}public static void main(String[] args) throws IOException {externalSort("input.txt", "output.txt");}
}
优化
- 定义:通过减少磁盘 I/O 次数和提高内存利用率来优化排序过程。
- 要点:
- 增加内存:使用更大的内存缓冲区,减少磁盘读写次数。
- 并行处理:使用多线程或多进程并行进行排序和归并操作。
- 优化归并算法:采用更高效的多路归并算法。
8. 平时怎么写数据库的模糊查询
- 定义:数据库模糊查询通过通配符来实现,常见的通配符有
%
(匹配任意数量的任意字符)和_
(匹配单个任意字符)。 - 要点:
LIKE
关键字:用于指定模糊查询条件。- 通配符位置:通配符的位置会影响查询范围和性能。
- 应用:在 MySQL 中,可以使用
REGEXP
进行正则表达式模糊查询。 - 代码示例(SQL):
sql
-- 查询以 'abc' 开头的产品记录
SELECT * FROM products WHERE product_name LIKE 'abc%';
-- 查询包含 'abc' 的产品记录
SELECT * FROM products WHERE product_name LIKE '%abc%';
9. 数据库里有 10000000 条用户信息, 需要给每位用户发送信息(必须发送成功) , 要求节省内存,具体如何实现
- 定义:采用分批处理的方式,每次从数据库中取出一定数量的用户信息,处理完后释放内存,再取下一批数据,确保每条信息都能成功发送。
- 要点:
- 分页查询:使用
LIMIT
和OFFSET
进行分页查询。 - 事务处理:确保每条信息发送成功,可使用数据库事务。
- 资源释放:及时释放数据库连接和内存资源。
- 分页查询:使用
- 应用:可使用消息队列异步处理信息发送,提高系统的吞吐量。
- 代码示例(Java + JDBC):
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;public class UserMessageSender {private static final int BATCH_SIZE = 1000;public static void main(String[] args) {String url = "jdbc:mysql://localhost:3306/test";String user = "root";String password = "password";try (Connection conn = DriverManager.getConnection(url, user, password)) {int offset = 0;while (true) {String sql = "SELECT user_id, email FROM users LIMIT " + BATCH_SIZE + " OFFSET " + offset;try (Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql)) {if (!rs.next()) {break;}do {int userId = rs.getInt("user_id");String email = rs.getString("email");// 发送信息逻辑sendMessage(email);} while (rs.next());}offset += BATCH_SIZE;}} catch (Exception e) {e.printStackTrace();}}private static void sendMessage(String email) {// 实现信息发送逻辑System.out.println("Sending message to: " + email);}
}
10. 如何进行数据库设计与优化
数据库设计
- 定义:根据业务需求,设计数据库的结构,包括表、字段、关系等,遵循数据库设计的范式,以确保数据的一致性和完整性。
- 要点:
- 需求分析:明确业务需求和数据流程。
- 概念设计:绘制 E - R 图,确定实体和关系。
- 逻辑设计:将 E - R 图转换为关系模型。
- 物理设计:选择合适的数据库管理系统和存储引擎,设计表结构和索引。
- 应用:考虑数据库的扩展性和性能,为未来业务发展预留空间。
数据库优化
- 定义:通过优化数据库结构、查询语句、索引等方式,提高数据库的性能和响应速度。
- 要点:
- 索引优化:合理创建索引,避免过多或不必要的索引。
- 查询优化:优化 SQL 语句,避免全表扫描和复杂子查询。
- 表结构优化:合理设计表结构,避免数据冗余。
- 数据库配置优化:调整数据库的参数配置,如内存分配、缓存大小等。
- 应用:定期进行数据库性能监测和分析,根据监测结果进行优化调整。
友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读
https://download.csdn.net/download/ylfhpy/90562306