《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux... 。
文章目录
- 一、本文面试题目录
-
-
- [21. 写出查询表中重复数据并删除的SQL语句。](#21. 写出查询表中重复数据并删除的SQL语句。)
- [22. 如何实现批量插入数据?COPY命令与INSERT相比有什么优势?](#22. 如何实现批量插入数据?COPY命令与INSERT相比有什么优势?)
- [23. 解释窗口函数(Window Function)的作用,举例说明ROW_NUMBER()、RANK()的用法。](#23. 解释窗口函数(Window Function)的作用,举例说明ROW_NUMBER()、RANK()的用法。)
- [24. 如何使用JOIN(内连接、左连接、全连接)实现多表关联查询?](#24. 如何使用JOIN(内连接、左连接、全连接)实现多表关联查询?)
- [25. 写出按条件更新数据的SQL,如何避免更新时的锁冲突?](#25. 写出按条件更新数据的SQL,如何避免更新时的锁冲突?)
- [26. 如何查询表的大小、索引大小?使用哪些系统函数?](#26. 如何查询表的大小、索引大小?使用哪些系统函数?)
- [27. 什么是临时表?临时表的生命周期如何管理?](#27. 什么是临时表?临时表的生命周期如何管理?)
- [28. 如何实现数据的导入导出(如导出为CSV、导入外部数据)?](#28. 如何实现数据的导入导出(如导出为CSV、导入外部数据)?)
- [29. 解释聚合函数(如SUM、COUNT)与GROUP BY的使用场景,如何处理NULL值?](#29. 解释聚合函数(如SUM、COUNT)与GROUP BY的使用场景,如何处理NULL值?)
- [30. 如何使用正则表达式进行模糊查询?举例说明~、~*操作符的区别。](#30. 如何使用正则表达式进行模糊查询?举例说明、*操作符的区别。)
- [31. 写出查询某列非空且满足特定条件的SQL,如何优化这类查询的性能?](#31. 写出查询某列非空且满足特定条件的SQL,如何优化这类查询的性能?)
- [32. 如何创建和使用函数(Function)?函数与存储过程(Procedure)的区别是什么?](#32. 如何创建和使用函数(Function)?函数与存储过程(Procedure)的区别是什么?)
- [33. 如何使用触发器(Trigger)实现数据的自动校验或日志记录?](#33. 如何使用触发器(Trigger)实现数据的自动校验或日志记录?)
- [34. 如何查询慢查询日志?如何分析慢查询的瓶颈?](#34. 如何查询慢查询日志?如何分析慢查询的瓶颈?)
- [35. 解释DISTINCT与DISTINCT ON的区别,举例说明DISTINCT ON的用法。](#35. 解释DISTINCT与DISTINCT ON的区别,举例说明DISTINCT ON的用法。)
- [36. PostgreSQL有哪些索引类型?GiST、GIN索引的适用场景是什么?](#36. PostgreSQL有哪些索引类型?GiST、GIN索引的适用场景是什么?)
- [37. 什么是部分索引(Partial Index)?如何通过部分索引优化查询性能?](#37. 什么是部分索引(Partial Index)?如何通过部分索引优化查询性能?)
- [38. 解释索引选择性(Selectivity)的概念,如何判断索引是否有效?](#38. 解释索引选择性(Selectivity)的概念,如何判断索引是否有效?)
- [39. 为什么有时索引会失效?列举导致索引失效的常见情况。](#39. 为什么有时索引会失效?列举导致索引失效的常见情况。)
- [40. 如何查看查询的执行计划?如何通过执行计划判断是否使用了索引?](#40. 如何查看查询的执行计划?如何通过执行计划判断是否使用了索引?)
-
一、本文面试题目录
21. 写出查询表中重复数据并删除的SQL语句。
查询重复数据 (以users
表的email
列为例):
sql
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
删除重复数据(保留最小id
的记录):
sql
DELETE FROM users
WHERE id NOT IN (
SELECT MIN(id)
FROM users
GROUP BY email
);
22. 如何实现批量插入数据?COPY命令与INSERT相比有什么优势?
批量插入方法:
-
多值INSERT :
sqlINSERT INTO users (name, age) VALUES ('Alice', 25), ('Bob', 30), ('Charlie', 35);
-
COPY命令 (从文件导入):
bashpsql -c "\COPY users (name, age) FROM '/path/to/data.csv' WITH CSV HEADER"
COPY优势:
- 性能:比INSERT快10-100倍,直接写入数据文件,跳过SQL解析。
- 事务安全:支持在事务中执行,失败时自动回滚。
- 处理大文件:适合导入GB级数据,内存占用低。
23. 解释窗口函数(Window Function)的作用,举例说明ROW_NUMBER()、RANK()的用法。
窗口函数作用 :
在分组数据上执行计算,不合并结果行,保留原始记录。语法:
sql
FUNCTION() OVER (PARTITION BY col ORDER BY col)
示例(按部门排序员工):
sql
SELECT
name,
dept_id,
salary,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dense_rank
FROM employees;
- ROW_NUMBER():连续排名(1,2,3),即使值相同。
- RANK():跳跃排名(1,1,3),相同值排名相同,后续排名跳过。
24. 如何使用JOIN(内连接、左连接、全连接)实现多表关联查询?
示例表结构:
sql
CREATE TABLE departments (id INT PRIMARY KEY, name TEXT);
CREATE TABLE employees (id INT PRIMARY KEY, name TEXT, dept_id INT REFERENCES departments(id));
JOIN类型:
-
内连接(INNER JOIN) :只返回匹配的记录。
sqlSELECT e.name, d.name FROM employees e INNER JOIN departments d ON e.dept_id = d.id;
-
左连接(LEFT JOIN) :返回左表所有记录,右表无匹配时补NULL。
sqlSELECT e.name, d.name FROM employees e LEFT JOIN departments d ON e.dept_id = d.id;
-
全连接(FULL OUTER JOIN) :返回左右表所有记录,无匹配时补NULL。
sqlSELECT e.name, d.name FROM employees e FULL OUTER JOIN departments d ON e.dept_id = d.id;
25. 写出按条件更新数据的SQL,如何避免更新时的锁冲突?
更新示例(将部门ID为1的员工工资提高10%):
sql
UPDATE employees
SET salary = salary * 1.1
WHERE dept_id = 1;
避免锁冲突的方法:
-
分批更新 :
sqlUPDATE employees SET salary = salary * 1.1 WHERE dept_id = 1 LIMIT 1000; -- 每次更新1000条,循环执行
-
降低隔离级别 :使用
READ COMMITTED
(默认)而非REPEATABLE READ
。 -
减少锁持有时间:避免在事务中执行耗时操作。
-
使用HOT UPDATE:确保更新时不改变索引列,减少索引锁。
26. 如何查询表的大小、索引大小?使用哪些系统函数?
查询表和索引大小:
sql
-- 表大小(包含TOAST和空闲空间)
SELECT pg_size_pretty(pg_total_relation_size('table_name'));
-- 索引大小总和
SELECT pg_size_pretty(
pg_indexes_size('table_name')
);
-- 每个索引的大小
SELECT indexrelname, pg_size_pretty(pg_relation_size(indexrelid))
FROM pg_stat_user_indexes
WHERE relname = 'table_name';
关键函数:
pg_total_relation_size()
:表总大小(含索引、TOAST)。pg_relation_size()
:表或索引的原始大小。pg_size_pretty()
:将字节转换为易读单位(如MB、GB)。
27. 什么是临时表?临时表的生命周期如何管理?
临时表 :
临时表是会话级或事务级的表,数据仅对当前会话/事务可见。
创建与生命周期:
sql
-- 会话级临时表(会话结束时自动删除)
CREATE TEMP TABLE temp_users (id INT, name TEXT);
-- 事务级临时表(事务结束时自动删除)
CREATE TEMP TABLE temp_logs (msg TEXT) ON COMMIT DROP;
特点:
- 数据存储在
pg_temp_*
模式中,与主表隔离。 - 可提高复杂查询性能(如缓存中间结果)。
28. 如何实现数据的导入导出(如导出为CSV、导入外部数据)?
导出为CSV:
bash
# 方法1:使用psql \copy命令(客户端执行)
psql -c "\COPY users TO '/path/to/export.csv' WITH CSV HEADER"
# 方法2:使用SQL(服务端执行,需文件权限)
COPY users TO '/var/lib/postgresql/export.csv' WITH CSV HEADER;
导入CSV:
bash
psql -c "\COPY users FROM '/path/to/import.csv' WITH CSV HEADER"
注意事项:
\copy
由客户端处理,COPY
由服务端处理。- 服务端路径需为PostgreSQL用户可访问的位置。
29. 解释聚合函数(如SUM、COUNT)与GROUP BY的使用场景,如何处理NULL值?
聚合函数与GROUP BY:
- 聚合函数 :对一组值执行计算(如
SUM()
、COUNT()
、AVG()
)。 - GROUP BY:将结果按指定列分组,每组应用聚合函数。
示例:
sql
-- 计算各部门的总工资(排除NULL值)
SELECT dept_id, SUM(salary)
FROM employees
GROUP BY dept_id;
-- 计算员工总数(包含NULL值的行)
SELECT COUNT(*) FROM employees;
-- 计算非NULL值的数量
SELECT COUNT(salary) FROM employees;
处理NULL值:
- 使用
COALESCE(salary, 0)
将NULL替换为0。 COUNT(*)
统计所有行,COUNT(col)
忽略NULL值。
30. 如何使用正则表达式进行模糊查询?举例说明、*操作符的区别。
正则表达式查询:
~
:大小写敏感匹配。~*
:大小写不敏感匹配。
示例:
sql
-- 查询以"Mr."开头的名字(大小写敏感)
SELECT * FROM users WHERE name ~ '^Mr\.';
-- 查询包含数字的邮箱(大小写不敏感)
SELECT * FROM users WHERE email ~* '[0-9]+';
常用元字符:
^
:行首匹配。$
:行尾匹配。.
:任意单个字符。*
:零次或多次重复。+
:一次或多次重复。
31. 写出查询某列非空且满足特定条件的SQL,如何优化这类查询的性能?
查询示例(查找年龄非空且大于30的员工):
sql
SELECT * FROM employees
WHERE age IS NOT NULL AND age > 30;
优化方法:
-
创建索引 :
sqlCREATE INDEX idx_age ON employees (age) WHERE age IS NOT NULL; -- 部分索引
-
避免函数操作 :
sql-- 低效:函数导致索引失效 WHERE UPPER(name) = 'JOHN'; -- 高效:使用表达式索引 CREATE INDEX idx_name_upper ON employees (UPPER(name));
-
统计信息更新 :
sqlANALYZE employees; -- 更新统计信息,帮助优化器选择索引
32. 如何创建和使用函数(Function)?函数与存储过程(Procedure)的区别是什么?
创建函数(计算部门平均工资):
sql
CREATE FUNCTION get_dept_avg(dept_id INT)
RETURNS NUMERIC AS $$
BEGIN
RETURN (
SELECT AVG(salary)
FROM employees
WHERE employees.dept_id = get_dept_avg.dept_id
);
END;
$$ LANGUAGE plpgsql;
调用函数:
sql
SELECT get_dept_avg(1);
函数 vs 存储过程:
特性 | 函数(Function) | 存储过程(Procedure) |
---|---|---|
返回值 | 必须返回值(RETURNS ) |
无返回值(VOID ) |
事务控制 | 不可使用COMMIT /ROLLBACK |
可控制事务 |
调用方式 | 在SQL表达式中调用 | 使用CALL 语句调用 |
副作用 | 应无副作用(幂等) | 可执行修改操作 |
33. 如何使用触发器(Trigger)实现数据的自动校验或日志记录?
示例1:自动校验(禁止删除活跃用户)
sql
CREATE FUNCTION prevent_active_user_deletion()
RETURNS TRIGGER AS $$
BEGIN
IF OLD.status = 'active' THEN
RAISE EXCEPTION 'Cannot delete active user';
END IF;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_active_user
BEFORE DELETE ON users
FOR EACH ROW EXECUTE FUNCTION prevent_active_user_deletion();
示例2:日志记录(记录员工工资变更)
sql
CREATE TABLE salary_log (
emp_id INT,
old_salary NUMERIC,
new_salary NUMERIC,
changed_at TIMESTAMP DEFAULT NOW()
);
CREATE FUNCTION log_salary_change()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO salary_log (emp_id, old_salary, new_salary)
VALUES (OLD.id, OLD.salary, NEW.salary);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER salary_change_log
AFTER UPDATE OF salary ON employees
FOR EACH ROW EXECUTE FUNCTION log_salary_change();
34. 如何查询慢查询日志?如何分析慢查询的瓶颈?
启用慢查询日志 (修改postgresql.conf
):
conf
log_statement = 'all' # 记录所有SQL
log_min_duration_statement = 1000 # 记录执行时间超过1秒的查询
分析方法:
-
查看日志文件 (通常位于
pg_log/
目录):bashgrep 'duration' /var/lib/postgresql/data/pg_log/postgresql-*.log
-
使用
EXPLAIN ANALYZE
:sqlEXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 123;
-
关注指标 :
cost
:优化器估算的执行成本。actual time
:实际执行时间。rows
:处理的行数。
35. 解释DISTINCT与DISTINCT ON的区别,举例说明DISTINCT ON的用法。
区别:
- DISTINCT:对结果集的所有列去重。
- DISTINCT ON:对指定列去重,保留每行的首条记录。
示例(保留每个部门工资最高的员工):
sql
SELECT DISTINCT ON (dept_id)
dept_id, name, salary
FROM employees
ORDER BY dept_id, salary DESC; -- 按工资降序,确保最高工资优先
结果:
dept_id | name | salary |
---|---|---|
1 | Alice | 8000 |
2 | Bob | 7500 |
36. PostgreSQL有哪些索引类型?GiST、GIN索引的适用场景是什么?
索引类型:
- B-Tree(默认,支持
=
、<
、>
等) - Hash(仅支持
=
) - GiST(通用搜索树)
- GIN(倒排索引)
- BRIN(块范围索引)
- SP-GiST(空间分区GiST)
GiST适用场景:
- 空间数据(如PostGIS的几何类型)。
- 全文搜索(如
tsvector
类型)。 - 范围查询(如
int4range
)。
GIN适用场景:
- 多值类型(如数组、JSONB)。
- 全文搜索(支持多个关键词查询)。
示例:
sql
-- GiST索引(用于几何类型)
CREATE INDEX idx_geom ON places USING GIST (location);
-- GIN索引(用于JSONB)
CREATE INDEX idx_data ON events USING GIN (data jsonb_path_ops);
37. 什么是部分索引(Partial Index)?如何通过部分索引优化查询性能?
部分索引 :
仅对表中符合条件的行创建索引,减少索引大小,提高查询效率。
示例(仅索引活跃用户):
sql
CREATE INDEX idx_active_users ON users (email)
WHERE status = 'active';
优化场景:
- 过滤频繁的数据 :如
WHERE deleted = false
(软删除场景)。 - 覆盖索引:包含查询所需的所有列,避免回表。
sql
CREATE INDEX idx_cover ON orders (customer_id)
INCLUDE (order_date, total) -- PostgreSQL 11+支持INCLUDE
WHERE status = 'paid';
38. 解释索引选择性(Selectivity)的概念,如何判断索引是否有效?
索引选择性 :
索引列中不同值的比例,计算公式:
选择性 = 唯一值数量 / 总行数
选择性越高(接近1),索引效率越好。
判断索引有效性:
-
查询执行计划:
sqlEXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
- 若显示
Index Scan
或Bitmap Index Scan
,索引有效。 - 若显示
Seq Scan
,可能索引未生效。
- 若显示
-
统计信息:
sqlSELECT relname, reltuples, relpages FROM pg_class WHERE relname = 'users';
- 行数(
reltuples
)与页数量(relpages
)比值低时,顺序扫描可能更快。
- 行数(
39. 为什么有时索引会失效?列举导致索引失效的常见情况。
索引失效原因:
-
表达式或函数操作 :
sqlWHERE UPPER(name) = 'JOHN'; -- 函数导致索引失效
-
隐式类型转换 :
sqlWHERE id = '123'; -- 若id为INT,字符串比较会触发全表扫描
-
低选择性数据 :
sqlWHERE is_active = true; -- 若90%的行都是true,索引可能不被使用
-
统计信息过时 :
sqlANALYZE users; -- 更新统计信息
-
错误的查询条件 :
sqlWHERE name LIKE '%john'; -- 以通配符开头的LIKE无法使用索引
40. 如何查看查询的执行计划?如何通过执行计划判断是否使用了索引?
查看执行计划:
sql
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 123 AND order_date > '2023-01-01';
关键判断指标:
-
操作类型:
Index Scan
:使用索引扫描。Bitmap Index Scan
:使用位图索引。Seq Scan
:全表扫描(未使用索引)。
-
索引名称:
-> Index Scan using idx_customer_id on orders -- 明确使用了idx_customer_id索引
-
成本与行数:
cost=0.42..8.44
:估算成本。rows=1
:估算返回行数,与实际行数(actual rows
)对比。
优化建议:
- 若高选择性查询仍使用全表扫描,检查统计信息(
ANALYZE
)。 - 若索引扫描行数过多,考虑调整查询条件或创建覆盖索引。