JDBC-sql
数据库核心概念详解
1. SQL语言基础
1.1 DDL (数据定义语言)
- CREATE: 创建数据库对象
sql CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE ); - ALTER: 修改数据库对象
sql ALTER TABLE users ADD COLUMN age INT; ALTER TABLE users MODIFY email VARCHAR(150); - DROP: 删除数据库对象
sql DROP TABLE users; DROP DATABASE test_db; - TRUNCATE: 清空表数据
sql TRUNCATE TABLE users;
1.2 DML (数据操作语言)
- SELECT: 查询数据
sql SELECT * FROM users WHERE age > 18; SELECT username, email FROM users ORDER BY username; - INSERT: 插入数据
sql INSERT INTO users (username, email) VALUES ('张三', 'zhangsan@example.com'); INSERT INTO users VALUES (1, '李四', 'lisi@example.com', 25); - UPDATE: 更新数据
sql UPDATE users SET age = 26 WHERE username = '张三'; - DELETE: 删除数据
sql DELETE FROM users WHERE age < 18;
1.3 DCL (数据控制语言)
- GRANT: 授权
```sql
-- MySQL示例
GRANT SELECT, INSERT ON database.users TO 'user1'@'localhost';
-- PostgreSQL数据库级别授权示例
-- 数据库信息:
-- 地址: 43.143.222.161
-- 端口: 5432
-- 用户: root
-- 授权用户: abc
-- 1. 数据库级别授权
-- 创建用户(如果不存在)
CREATE USER abc WITH PASSWORD 'your_password';
-- 授权thought数据库的INSERT权限
\c thought root
GRANT CONNECT ON DATABASE thought TO abc;
GRANT USAGE ON SCHEMA public TO abc;
GRANT INSERT ON ALL TABLES IN SCHEMA public TO abc;
-- 对未来创建的表也自动授权
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT INSERT ON TABLES TO abc;
-- 2. Schema级别授权
-- 连接到think数据库并授权think schema
\c think root
GRANT USAGE ON SCHEMA think TO abc;
GRANT INSERT ON ALL TABLES IN SCHEMA think TO abc;
-- 对未来创建的表也自动授权
ALTER DEFAULT PRIVILEGES IN SCHEMA think GRANT INSERT ON TABLES TO abc;
-- 3. 表级别授权
-- 授权think schema下的特定表
\c think root
GRANT INSERT ON TABLE think.specific_table1 TO abc;
GRANT INSERT ON TABLE think.specific_table2 TO abc;
-- 4. 查看权限
\du abc -- 查看用户权限
\z think.specific_table1 -- 查看特定表权限
-- 连接字符串示例
-- thought数据库连接
-- postgresql://abc:your_password@43.143.222.161:5432/thought
-- think数据库连接
-- postgresql://abc:your_password@43.143.222.161:5432/think
- REVOKE: 撤销权限sql
-- MySQL示例
REVOKE INSERT ON database.users FROM 'user1'@'localhost';
-- PostgreSQL撤销权限示例
-- 1. 撤销数据库级别权限
\c thought root
REVOKE INSERT ON ALL TABLES IN SCHEMA public FROM abc;
REVOKE USAGE ON SCHEMA public FROM abc;
REVOKE CONNECT ON DATABASE thought FROM abc;
-- 2. 撤销Schema级别权限
\c think root
REVOKE INSERT ON ALL TABLES IN SCHEMA think FROM abc;
REVOKE USAGE ON SCHEMA think FROM abc;
-- 3. 撤销表级别权限
REVOKE INSERT ON TABLE think.specific_table1 FROM abc;
REVOKE INSERT ON TABLE think.specific_table2 FROM abc;
-- 4. 撤销默认权限
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE INSERT ON TABLES FROM abc;
ALTER DEFAULT PRIVILEGES IN SCHEMA think REVOKE INSERT ON TABLES FROM abc;
-- 5. 完全删除用户(如果需要)
DROP USER IF EXISTS abc;
- COMMIT: 提交事务sql
COMMIT;
- ROLLBACK: 回滚事务sql
ROLLBACK;
```
1.4 常用SQL函数
- 聚合函数
- COUNT(): 计数
- SUM(): 求和
- AVG(): 平均值
- MAX(): 最大值
- MIN(): 最小值
- 字符串函数
- CONCAT(): 字符串连接
- SUBSTRING(): 子字符串
- UPPER()/LOWER(): 大小写转换
- 日期函数
- NOW(): 当前日期时间
- DATE_FORMAT(): 日期格式化
- DATEDIFF(): 日期差值
2. 事务处理
2.1 事务特性 (ACID)
- 原子性(Atomicity): 事务要么全部执行,要么全部不执行
sql BEGIN TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT; - 一致性(Consistency): 事务执行前后数据库状态保持一致
- 隔离性(Isolation): 事务之间相互隔离
- 持久性(Durability): 事务提交后数据永久保存
2.2 事务隔离级别
-
读未提交(Read Uncommitted)
- 最低隔离级别
- 可能发生脏读、不可重复读、幻读
sql SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -
读已提交(Read Committed)
- 防止脏读
- 可能发生不可重复读、幻读
sql SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -
可重复读(Repeatable Read)
- 防止脏读、不可重复读
- 可能发生幻读
sql SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; -
串行化(Serializable)
- 最高隔离级别
- 完全防止脏读、不可重复读、幻读
- 性能最差
sql SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
2.3 事务并发问题
- 脏读:读取到未提交的数据
- 不可重复读:同一事务中多次读取同一数据得到不同结果
- 幻读:同一事务中多次查询返回的结果集不一致
- 丢失更新:多个事务同时更新导致更新丢失
3. 数据库连接池
3.1 连接池优势
- 减少连接创建和销毁的开销
- 提高系统响应速度
- 控制连接数量,防止系统过载
- 统一管理数据库连接
3.2 常见连接池实现及对比
3.2.1 HikariCP
- 优势:
- 性能最佳,号称"光速"连接池
- 代码精简,仅仅14KB
- 零配置,可以直接使用
- 可靠性高,在高并发下表现稳定
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(10);
HikariDataSource dataSource = new HikariDataSource(config);
3.2.2 Druid
- 优势:
- 强大的监控功能
- 防SQL注入
- 详细的统计信息
- 扩展性好
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
dataSource.setInitialSize(5);
dataSource.setMaxActive(10);
3.2.3 C3P0
- 优势:
- 成熟稳定
- 文档齐全
- Spring框架集成良好
- 劣势:
- 性能相对较差
- 更新较慢
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
cpds.setUser("root");
cpds.setPassword("password");
3.2.4 DBCP
- 优势:
- Apache出品
- 配置简单
- 支持JNDI
- 劣势:
- 性能一般
- 不支持高并发
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("password");
3.2.5 PostgreSQL内置连接池
- PgJDBC (PostgreSQL JDBC Driver) 本身不提供连接池
- PostgreSQL服务器端提供连接池功能:
```sql
-- 查看当前连接池设置
SHOW max_connections;
SHOW superuser_reserved_connections;
-- 设置连接池参数(需要在postgresql.conf中配置)
max_connections = 100 # 最大连接数
superuser_reserved_connections = 3 # 为超级用户保留的连接数
```
- 推荐在应用层使用专业连接池:
1. HikariCP(推荐):
java HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost:5432/dbname"); config.setUsername("postgres"); config.setPassword("password"); config.setMaximumPoolSize(10); HikariDataSource ds = new HikariDataSource(config);
- PgBouncer(PostgreSQL专用连接池):
```ini
[databases]
* = host=localhost port=5432 dbname=mydb
[pgbouncer]
listen_port = 6432
listen_addr = localhost
auth_type = md5
pool_mode = session
max_client_conn = 100
default_pool_size = 20
```
3.3 连接池性能对比
| 连接池 | 并发性能 | 功能特性 | 监控支持 | 使用难度 | 框架集成 |
|---|---|---|---|---|---|
| HikariCP | ★★★★★ | ★★★★ | ★★★ | ★★★★★ | ★★★★★ |
| Druid | ★★★★ | ★★★★★ | ★★★★★ | ★★★ | ★★★★ |
| C3P0 | ★★★ | ★★★ | ★★ | ★★★★ | ★★★★ |
| DBCP | ★★★ | ★★★ | ★★ | ★★★★ | ★★★ |
3.4 连接池配置参数
- 基础配置
- 初始连接数:池启动时创建的连接数
- 最大连接数:池中最多可容纳的连接数
-
最小空闲连接:池中最少应保持的空闲连接数
-
性能相关
- 获取连接超时时间:等待可用连接的最长时间
- 空闲连接超时时间:空闲连接被回收前的最长时间
- 最大生命周期:连接被强制关闭前的最长时间
-
验证查询:用于测试连接是否可用的SQL
-
监控相关
- 是否开启JMX监控
- 是否记录慢SQL
- 是否开启监控页面(Druid特有)
4. 事务管理
4.1 编程式事务管理
- 通过代码显式控制事务
java TransactionTemplate template = new TransactionTemplate(transactionManager); template.execute(new TransactionCallback<Object>() { public Object doInTransaction(TransactionStatus status) { // 事务操作 return null; } }); - 使用 TransactionTemplate
- 灵活但代码侵入性强
4.2 声明式事务管理
- 通过注解或XML配置
java @Transactional(propagation = Propagation.REQUIRED) public void transferMoney(String fromAccount, String toAccount, double amount) { // 转账操作 } - 使用 @Transactional
- 代码侵入性小,易于维护
4.3 Spring事务实现原理
4.3.1 事务实现机制
-
代理方式:
```java
// Spring使用AOP实现事务管理
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;@Transactional
public void method1() {
// 有事务
method2(); // 内部调用,method2的@Transactional注解无效
}@Transactional
public void method2() {
// 如果是内部调用(即从method1调用),注解无效
// 如果是外部调用(即通过代理对象调用),注解生效
}// 通过代理对象调用的正确方式
public void method3() {
UserService proxy = AopContext.currentProxy();
proxy.method2(); // 通过代理调用,method2的事务会生效
}
}
``` -
事务生效条件:
- 方法必须是public
- 必须通过代理对象调用(外部调用或显式获取代理对象调用)
- 类内部直接调用不会触发新事务(因为绕过了代理)
- 同一个类中,如果A方法有事务,直接调用同类的B方法,B方法的事务注解会失效 -
调用方式对比:
```java
@Service
public class UserService {
// 场景1:外部调用
@Transactional
public void methodA() {
// 有事务,因为是通过代理对象调用
}// 场景2:内部调用
@Transactional
public void methodB() {
methodC(); // methodC的事务注解无效,因为是内部调用
}@Transactional
public void methodC() {
// 如果从methodB调用,注解无效
// 如果从外部调用,注解生效
}// 场景3:通过代理对象调用
@Transactional
public void methodD() {
UserService proxy = AopContext.currentProxy();
proxy.methodC(); // methodC的事务注解生效,因为通过代理调用
}
}
// 使用示例
@Service
public class OrderService {
@Autowired
private UserService userService; // 注入的是代理对象
public void test() {
userService.methodA(); // 事务生效,外部调用
userService.methodC(); // 事务生效,外部调用
}
}
```
-
事务生效的关键点:
- 外部调用:通过其他类注入的Service调用方法,会走代理
- 内部调用:同一个类中的方法直接调用,不会走代理
- 代理对象调用:显式获取代理对象后调用,会走代理
```java
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象@Transactional
public void method1() {
// 方式1:注入自身代理对象
self.method2(); // 事务生效// 方式2:通过AopContext获取代理对象 UserService proxy = AopContext.currentProxy(); proxy.method2(); // 事务生效 // 方式3:直接调用(错误方式) method2(); // 事务失效}
@Transactional
public void method2() {
// 业务逻辑
}
}
``` -
最佳实践:
```java
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象// 外层方法负责事务控制
@Transactional
public void businessMethod() {
// 1. 使用注入的代理对象调用
self.method1();
self.method2();// 2. 或者使用AopContext(需要额外配置) UserService proxy = AopContext.currentProxy(); proxy.method1(); proxy.method2();}
// 内层方法专注业务逻辑
public void method1() {
// 业务逻辑
}public void method2() {
// 业务逻辑
}
}
```
4.3.2 事务失效场景
-
方法不是public:
java @Service public class UserService { @Transactional private void method() { // 事务无效 // ... } } -
自调用问题:
```java
@Service
public class UserService {
@Transactional
public void method1() {
method2(); // method2的事务不会生效
}@Transactional
public void method2() {
// ...
}
}
``` -
解决自调用:
```java
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象@Transactional
public void method1() {
self.method2(); // method2的事务会生效
}@Transactional
public void method2() {
// ...
}
}
```
4.3.3 事务传播实现
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional(propagation = Propagation.REQUIRED)
public void createUserWithOrder() {
// REQUIRED:使用当前事务,如果没有则创建新事务
saveUser();
orderService.createOrder(); // 使用当前事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createUserWithNewOrder() {
// REQUIRES_NEW:总是创建新事务
saveUser();
try {
orderService.createOrder(); // 新事务
} catch (Exception e) {
// 订单创建失败不影响用户创建
}
}
}
4.3.4 事务源码实现
-
事务代理创建:
java // Spring通过AOP创建事务代理 @EnableTransactionManagement // 启用注解事务管理 public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } } -
事务拦截器:
java // 简化的事务拦截器逻辑 public class TransactionInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { TransactionInfo txInfo = createTransactionIfNecessary(); try { Object retVal = invocation.proceed(); txInfo.getTransactionManager().commit(); return retVal; } catch (Exception ex) { txInfo.getTransactionManager().rollback(); throw ex; } } }
4.3.5 最佳实践
-
正确使用事务:
```java
@Service
public class UserService {
@Autowired
private UserService self;// 外部方法控制事务
@Transactional
public void businessMethod() {
self.method1();
self.method2();
}// 内部方法专注业务
public void method1() {
// 业务逻辑
}public void method2() {
// 业务逻辑
}
}
``` -
避免事务失效:
- 确保方法是public
- 使用代理对象调用
- 合理设置传播行为
- 正确处理异常 -
性能优化:
- 精确控制事务范围
- 避免长事务
- 合理设置隔离级别
- 使用只读事务优化查询
5. 存储过程
5.1 存储过程优势
- 提高性能
- 减少网络传输
- 增强安全性
- 便于维护
- 可重用性高
5.2 存储过程示例
DELIMITER //
CREATE PROCEDURE get_employee(IN emp_id INT)
BEGIN
SELECT * FROM employees WHERE id = emp_id;
END //
CREATE PROCEDURE calculate_bonus(
IN emp_id INT,
IN base_salary DECIMAL(10,2),
OUT bonus DECIMAL(10,2)
)
BEGIN
DECLARE performance_score DECIMAL(3,2);
SELECT score INTO performance_score
FROM employee_performance
WHERE employee_id = emp_id;
SET bonus = base_salary * performance_score;
END //
DELIMITER ;
5.3 存储过程参数类型
- IN: 输入参数
- OUT: 输出参数
- INOUT: 输入输出参数
6. 数据类型
6.1 数值类型
- 整数类型
- TINYINT: -128 到 127
- SMALLINT: -32768 到 32767
- INT: -2^31 到 2^31-1
- BIGINT: -2^63 到 2^63-1
- 浮点类型
- FLOAT: 单精度浮点数
- DOUBLE: 双精度浮点数
- DECIMAL(M,D): 精确小数
6.2 字符串类型
- 定长字符串: CHAR(n)
- 固定长度,最大255字符
- 右侧填充空格
- 变长字符串: VARCHAR(n)
- 可变长度,最大65535字符
- 实际长度+1或2字节存储长度
- 文本类型
- TEXT: 最大65535字节
- MEDIUMTEXT: 最大16MB
- LONGTEXT: 最大4GB
6.3 日期时间类型
- DATE: 日期 'YYYY-MM-DD'
- TIME: 时间 'HH:MM:SS'
- DATETIME: 日期时间 'YYYY-MM-DD HH:MM:SS'
- TIMESTAMP: 时间戳
sql CREATE TABLE logs ( id INT PRIMARY KEY, action VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
6.4 二进制类型
- BINARY: 定长二进制
- VARBINARY: 变长二进制
- BLOB: 二进制大对象
- TINYBLOB: 最大255字节
- BLOB: 最大65KB
- MEDIUMBLOB: 最大16MB
- LONGBLOB: 最大4GB
7. 索引
7.1 索引类型
- 普通索引
sql CREATE INDEX idx_username ON users(username); - 唯一索引
sql CREATE UNIQUE INDEX idx_email ON users(email); - 主键索引
sql ALTER TABLE users ADD PRIMARY KEY (id); - 复合索引
sql CREATE INDEX idx_name_age ON users(name, age); - 全文索引
```sql
-- MySQL全文索引(仅InnoDB和MyISAM引擎支持)
-- 1. 创建表时指定
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT INDEX idx_content(content)
) ENGINE=InnoDB;
-- 2. 在已有表上创建
ALTER TABLE articles ADD FULLTEXT INDEX idx_title_content(title, content);
-- 或
CREATE FULLTEXT INDEX idx_title_content ON articles(title, content);
-- 3. 使用全文索引查询
-- 自然语言模式
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('搜索关键词' IN NATURAL LANGUAGE MODE);
-- BOOLEAN模式(支持更复杂的查询)
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('+必须包含 -不能包含 >提升权重' IN BOOLEAN MODE);
-- 查询扩展模式
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('关键词' WITH QUERY EXPANSION);
```
MySQL全文索引注意事项:
1. 支持的存储引擎:
- MySQL 5.6以前只有MyISAM支持
- MySQL 5.6及以后InnoDB也支持
-
限制条件:
- 最小索引长度(默认3个字符):
innodb_ft_min_token_size - 最大索引长度(默认84个字符):
innodb_ft_max_token_size - 停用词:默认会过滤掉一些常见词
- 最小索引长度(默认3个字符):
-
中文支持:
- 需要安装ngram解析器插件
```sql
-- 创建支持中文的全文索引
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT INDEX idx_content(content) WITH PARSER ngram
) ENGINE=InnoDB;
-- 设置ngram分词长度(默认2)
SET GLOBAL ngram_token_size = 2;
``` - 需要安装ngram解析器插件
-
性能考虑:
- 全文索引会占用较大空间
- 更新开销大
- 不适合频繁更新的列
- 建议针对大文本字段使用
-
查询示例:
```sql
-- 简单匹配
SELECT * FROM articles
WHERE MATCH(content) AGAINST('关键词');
-- 布尔模式示例
SELECT * FROM articles
WHERE MATCH(content) AGAINST('+必须包含 -排除词 >提升权重' IN BOOLEAN MODE);
-- 短语匹配
SELECT * FROM articles
WHERE MATCH(content) AGAINST('"完整短语"' IN BOOLEAN MODE);
-- 通配符匹配
SELECT * FROM articles
WHERE MATCH(content) AGAINST('关键词*' IN BOOLEAN MODE);
```
- PostgreSQL全文搜索
```sql
-- 使用tsvector和tsquery
CREATE INDEX idx_content_fts ON articles
USING GIN (to_tsvector('chinese', content));
-- 查询
SELECT * FROM articles
WHERE to_tsvector('chinese', content) @@ to_tsquery('chinese', '关键词');
```
7.2 索引匹配顺序
MySQL在执行查询时,会按照以下优先级顺序选择索引:
-
主键索引(PRIMARY KEY)
sql -- 主键索引优先级最高 SELECT * FROM users WHERE id = 1; -- 使用主键索引 -
唯一索引(UNIQUE)
sql -- 如果有多个唯一索引,优先选择区分度高的 SELECT * FROM users WHERE email = 'test@example.com'; -- 使用唯一索引 -
普通索引(INDEX/KEY)
- 单列索引
sql SELECT * FROM users WHERE username = 'test'; -- 使用普通索引
- 联合索引(遵循最左前缀原则)
```sql
-- 假设有索引:idx_name_age_city(name, age, city)
-- 完全匹配,使用索引
SELECT * FROM users WHERE name = 'Tom' AND age = 20 AND city = 'Beijing';
-- 匹配前缀,使用部分索引
SELECT * FROM users WHERE name = 'Tom' AND age = 20;
-- 跳过中间列,只能使用name列的索引
SELECT * FROM users WHERE name = 'Tom' AND city = 'Beijing';
-- 不符合最左前缀,不使用索引
SELECT * FROM users WHERE age = 20 AND city = 'Beijing';
```
-
前缀索引
sql -- 对字符串列的前N个字符建立的索引 CREATE INDEX idx_name_prefix ON users(name(10)); -
全文索引(FULLTEXT)
sql -- 全文索引只在MATCH AGAINST语句中使用 SELECT * FROM articles WHERE MATCH(content) AGAINST('keyword' IN NATURAL LANGUAGE MODE);
索引选择的影响因素:
-
区分度(Selectivity)
sql -- 查看索引的区分度 SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name; -
查询条件
```sql
-- 范围查询可能导致索引失效
SELECT * FROM users WHERE age > 20; -- 索引可能不被使用
-- 等值查询更容易使用索引
SELECT * FROM users WHERE age = 20; -- 索引更可能被使用
```
-
数据分布
sql -- 如果数据分布不均匀,优化器可能选择全表扫描 SELECT * FROM users WHERE status = 'active'; -- 如果大部分用户都是active,可能不使用索引 -
查询类型
```sql
-- ORDER BY 可能影响索引选择
SELECT * FROM users WHERE age > 20 ORDER BY created_at;
-- JOIN 条件也会影响索引选择
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.age > 20;
```
索引使用建议:
-
经常用于查询的列优先建立索引
sql -- 如果经常按用户名查询 CREATE INDEX idx_username ON users(username); -
区分度高的列优先建立索引
```sql
-- email通常区分度高,适合建立索引
CREATE UNIQUE INDEX idx_email ON users(email);
-- status可能只有几个值,区分度低,不适合单独建立索引
-- 但可以作为联合索引的一部分
CREATE INDEX idx_status_created ON users(status, created_at);
```
-
索引字段的顺序
sql -- 把选择性高的列放在联合索引的前面 CREATE INDEX idx_email_status ON users(email, status); -- 好 CREATE INDEX idx_status_email ON users(status, email); -- 不推荐 -
避免冗余索引
```sql
-- 已经有了(name, age)的联合索引
CREATE INDEX idx_name_age ON users(name, age);
-- 不需要再创建单独的name索引,因为联合索引的第一列可以单独使用
CREATE INDEX idx_name ON users(name); -- 冗余索引
```
8. 性能优化
8.1 查询优化
- 使用 EXPLAIN 分析查询
```sql
-- MySQL EXPLAIN示例
EXPLAIN SELECT * FROM users WHERE username = 'test';
-- PostgreSQL EXPLAIN ANALYZE示例
EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'test';
```
- 避免 SELECT *
- 使用合适的索引
- 优化 JOIN 查询
- 合理使用子查询
8.2 数据库设计优化
- 合理的表结构设计
- 适当的字段类型选择
- 规范的命名约束
- 合理的索引设计
- 适当的数据冗余
8.3 配置优化
- 缓冲池大小设置
- 连接池参数调优
- 日志级别配置
- 临时表空间设置
- 并发参数调整
8.4 SQL问题排查方法
8.4.1 慢查询日志分析
-
MySQL慢查询配置:
sql -- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; -- 设置慢查询阈值(秒) SET GLOBAL long_query_time = 1; -- 查看慢查询日志位置 SHOW VARIABLES LIKE '%slow_query%'; -
PostgreSQL慢查询配置:
```sql
-- postgresql.conf配置
log_min_duration_statement = 1000 -- 记录执行时间超过1秒的查询
log_destination = 'csvlog' -- 设置日志格式为CSV
logging_collector = on -- 开启日志收集
log_directory = 'log' -- 日志目录
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' -- 日志文件名格式
-- 查看当前设置
SHOW log_min_duration_statement;
SHOW logging_collector;
SHOW log_directory;
-- 查看慢查询的几种方法:
-- 1. 直接查看日志文件
-- 日志通常位于数据目录的pg_log或log目录下
-- 可以通过以下命令查看数据目录位置:
SHOW data_directory;
-- 2. 使用pg_stat_statements扩展(推荐)
-- 首先需要安装扩展
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 查看慢查询统计
SELECT
query,
calls,
total_exec_time,
min_exec_time,
max_exec_time,
mean_exec_time,
stddev_exec_time,
rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- 重置统计信息
SELECT pg_stat_statements_reset();
-- 3. 通过系统视图查看当前活动查询
SELECT
pid,
usename,
application_name,
client_addr,
query_start,
state,
wait_event_type,
wait_event,
query
FROM pg_stat_activity
WHERE state != 'idle'
AND query_start < current_timestamp - interval '1 second'
ORDER BY query_start;
-- 4. 使用管理视图查看统计信息
-- 查看表的访问统计
SELECT
schemaname,
relname,
seq_scan,
seq_tup_read,
idx_scan,
idx_tup_fetch
FROM pg_stat_user_tables
WHERE (seq_scan > 0 OR idx_scan > 0)
ORDER BY seq_scan DESC;
```
注意事项:
1. 日志配置修改需要重启PostgreSQL服务才能生效
2. pg_stat_statements是最推荐的慢查询分析工具,因为它:
- 提供详细的执行时间统计
- 可以看到SQL语句的具体执行次数
- 能够分析SQL语句的性能波动
- 支持实时监控
3. 日志文件的位置可能因安装方式不同而异:
- Ubuntu: /var/log/postgresql/
- CentOS: /var/lib/pgsql/data/pg_log/
- Docker: 需要映射日志目录
4. 为了更好地进行慢查询分析,建议同时配置以下参数:
```sql
-- 记录锁等待超过1秒的语句
log_lock_waits = on
log_min_duration_lock = 1000
-- 记录自动清理和分析的相关信息
log_autovacuum_min_duration = 1000
-- 记录执行计划信息
auto_explain.log_min_duration = 1000
```
8.4.2 执行计划分析
- MySQL执行计划:
```sql
-- 基本执行计划
EXPLAIN SELECT * FROM users WHERE id > 1000;
-- 详细执行计划
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE id > 1000;
-- 分析实际执行情况
EXPLAIN ANALYZE SELECT * FROM users WHERE id > 1000;
```
- PostgreSQL执行计划:
```sql
-- 显示执行计划(不执行)
EXPLAIN SELECT * FROM users WHERE id > 1000;
-- 显示执行计划并实际执行
EXPLAIN ANALYZE SELECT * FROM users WHERE id > 1000;
-- 显示缓冲区使用情况
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE id > 1000;
```
8.4.3 性能监控工具
- MySQL性能监控:
```sql
-- 查看当前执行的SQL
SHOW PROCESSLIST;
-- 查看InnoDB状态
SHOW ENGINE INNODB STATUS;
-- 查看表的统计信息
SHOW TABLE STATUS;
```
- PostgreSQL性能监控:
```sql
-- 查看活动查询
SELECT * FROM pg_stat_activity;
-- 查看表统计信息
SELECT * FROM pg_stat_user_tables;
-- 查看索引使用情况
SELECT * FROM pg_stat_user_indexes;
```
8.4.4 常见问题排查步骤
-
SQL语句层面:
- 检查WHERE条件是否合理
- 确认是否使用了适当的索引
- 检查JOIN条件和顺序
- 分析是否存在全表扫描
- 评估子查询性能 -
数据库配置层面:
- 检查数据库参数配置
- 评估内存使用情况
- 分析磁盘I/O状况
- 检查连接池状态 -
应用程序层面:
```java
// 使用PreparedStatement防止SQL注入
PreparedStatement pstmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
pstmt.setInt(1, userId);
// 批量处理提高性能
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO users (name, age) VALUES (?, ?)"
);
for (User user : users) {
pstmt.setString(1, user.getName());
pstmt.setInt(2, user.getAge());
pstmt.addBatch();
}
pstmt.executeBatch();
```
8.4.5 性能优化建议
-
SQL优化:
- 使用覆盖索引
- 避免使用SELECT *
- 控制JOIN表数量
- 使用合适的WHERE条件
- 适当使用索引提示 -
数据库优化:
- 定期更新统计信息
- 合理设置缓冲池大小
- 优化日志配置
- 定期维护索引
- 适当进行分区 -
应用优化:
- 使用连接池
- 实现数据缓存
- 合理使用批处理
- 优化事务范围
- 实现读写分离
8.4.6 监控指标
-
关键性能指标:
- 查询响应时间
- TPS(每秒事务数)
- QPS(每秒查询数)
- 缓存命中率
- 连接池使用率 -
系统资源指标:
- CPU使用率
- 内存使用情况
- 磁盘I/O负载
- 网络带宽使用率 -
数据库指标:
- 活动连接数
- 锁等待情况
- 临时表使用
- 慢查询数量
- 索引使用率
9. 搜索技术对比
9.1 向量搜索 vs Elasticsearch搜索
9.1.1 搜索准确性比较
- 向量搜索(Vector Search)
- 优势:- 更好的语义理解能力
- 能捕捉上下文和概念相似性
- 支持跨语言搜索
- 处理同义词和相关概念更准确
```python
# 向量搜索示例(使用FAISS)
import faiss
import numpy as np
# 创建向量索引
dimension = 128 # 向量维度
index = faiss.IndexFlatL2(dimension)
# 添加向量数据
vectors = np.random.random((1000, dimension)).astype('float32')
index.add(vectors)
# 搜索最相似的向量
query_vector = np.random.random(dimension).astype('float32')
D, I = index.search(query_vector.reshape(1, -1), k=5)
```
- Elasticsearch搜索
- 优势:- 精确的文本匹配
- 强大的分词和分析能力
- 支持复杂的布尔查询
- 良好的过滤和聚合能力
json // Elasticsearch查询示例 { "query": { "bool": { "must": [ { "match": { "title": "搜索关键词" } }, { "match_phrase": { "content": "精确短语" } } ], "filter": [ { "term": { "status": "active" } } ] } } }
9.1.2 适用场景对比
-
向量搜索更适合:
- 语义搜索需求
- 图像相似度搜索
- 推荐系统
- 跨语言搜索场景
- 概念相似性匹配 -
Elasticsearch更适合:
- 精确文本匹配
- 结构化数据搜索
- 聚合分析
- 实时搜索
- 全文检索
9.1.3 准确性影响因素
-
向量搜索准确性影响因素:
- 向量模型质量
- 向量维度选择
- 训练数据质量
- 相似度计算方法
- 向量索引类型 -
Elasticsearch准确性影响因素:
- 分词器选择
- 索引配置
- 查询策略
- 相关性算法
- 字段权重设置
9.1.4 性能对比
| 特性 | 向量搜索 | Elasticsearch |
|---|---|---|
| 语义理解 | ★★★★★ | ★★★ |
| 精确匹配 | ★★★ | ★★★★★ |
| 跨语言能力 | ★★★★★ | ★★ |
| 计算复杂度 | 较高 | 较低 |
| 存储需求 | 较大 | 中等 |
| 实时性 | 较好 | 很好 |
9.1.5 选择建议
-
使用向量搜索的情况:
- 需要深度语义理解
- 处理跨语言搜索
- 相似内容推荐
- 多模态搜索(文本+图像)
- 需要处理同义词和相关概念 -
使用Elasticsearch的情况:
- 精确文本匹配需求
- 结构化数据查询
- 需要复杂的过滤和聚合
- 实时性要求高
- 全文检索场景 -
混合使用的情况:
```python
# 混合搜索示例
def hybrid_search(query, weights=(0.7, 0.3)):
# 向量搜索结果
vector_results = vector_search(query)# ES搜索结果
es_results = es_search(query)# 结果融合
final_results = merge_results(
vector_results,
es_results,
weights
)return final_results
```
总的来说,向量搜索在语义理解和相似性匹配方面更准确,而Elasticsearch在精确文本匹配和结构化查询方面更准确。选择哪种技术取决于具体的应用场景和需求。在实际应用中,往往采用混合方案来综合两者的优势。