1. 项目概述
作为一名Java开发者,我最近决定重新系统性地梳理Java知识体系。在第三天,我意识到数据库操作是Java开发中不可或缺的核心技能,于是将重点转向SQL学习。SQL作为关系型数据库的标准查询语言,其重要性不言而喻——无论是简单的CRUD操作,还是复杂的数据分析,都离不开SQL的支持。
在实际开发中,我发现很多Java开发者(包括我自己)经常陷入"会用但不够精通"的状态。我们可能熟悉基本的SELECT语句,但对索引优化、事务隔离级别等进阶概念却一知半解。这种知识断层往往导致应用性能问题和难以排查的bug。因此,这次SQL学习我决定从基础语法开始,逐步深入到性能优化和实际应用场景。
2. SQL基础语法精要
2.1 数据定义语言(DDL)
DDL用于定义和管理数据库对象结构。创建表时,我特别注意字段类型的选择:
CREATE TABLE employees ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE, salary DECIMAL(10,2) CHECK (salary > 0), dept_id INT, hire_date DATE DEFAULT CURRENT_DATE, FOREIGN KEY (dept_id) REFERENCES departments(id) ) ENGINE=InnoDB;注意:VARCHAR长度应根据业务实际需求设置,过大会浪费存储空间。对于可能包含多字节字符(如中文)的字段,建议使用utf8mb4字符集。
2.2 数据操作语言(DML)
增删改查是SQL的核心操作。在INSERT时,我推荐明确指定列名:
-- 好的实践 INSERT INTO employees (name, email, salary) VALUES ('张三', 'zhangsan@example.com', 8000.00); -- 应避免的写法 INSERT INTO employees VALUES (1, '张三', ...);UPDATE操作要特别注意WHERE条件,避免误更新整表:
UPDATE employees SET salary = salary * 1.1 WHERE dept_id = 2 AND hire_date < '2020-01-01';2.3 数据查询语言(DQL)
SELECT语句的复杂度可以很高。基础查询:
SELECT e.name, d.department_name, e.salary FROM employees e JOIN departments d ON e.dept_id = d.id WHERE e.salary > 5000 ORDER BY e.salary DESC LIMIT 10;3. SQL进阶技巧
3.1 聚合函数与分组
统计各部门的平均薪资和人数:
SELECT d.department_name, AVG(e.salary) AS avg_salary, COUNT(e.id) AS employee_count FROM employees e JOIN departments d ON e.dept_id = d.id GROUP BY d.department_name HAVING AVG(e.salary) > 6000;提示:WHERE在分组前过滤行,HAVING在分组后过滤组。
3.2 子查询与派生表
找出薪资高于部门平均水平的员工:
SELECT e.name, e.salary, dept_avg.avg_salary FROM employees e JOIN ( SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id ) dept_avg ON e.dept_id = dept_avg.dept_id WHERE e.salary > dept_avg.avg_salary;3.3 窗口函数
计算员工的薪资排名(不改变结果集行数):
SELECT name, salary, RANK() OVER (ORDER BY salary DESC) AS salary_rank, salary - LAG(salary) OVER (ORDER BY salary DESC) AS diff_with_previous FROM employees;4. SQL性能优化
4.1 索引使用原则
为经常查询的列创建索引:
CREATE INDEX idx_employee_dept ON employees(dept_id); CREATE INDEX idx_employee_name ON employees(name);但要注意:
- 索引会降低INSERT/UPDATE/DELETE速度
- 小表不需要索引
- 区分度低的列(如性别)不适合单独建索引
4.2 EXPLAIN分析执行计划
EXPLAIN SELECT * FROM employees WHERE name LIKE '张%';关键指标:
- type:最好达到ref或range
- possible_keys:可能使用的索引
- rows:预估扫描行数
4.3 避免全表扫描
常见陷阱:
-- 使用函数导致索引失效 SELECT * FROM employees WHERE YEAR(hire_date) = 2020; -- 应改为 SELECT * FROM employees WHERE hire_date BETWEEN '2020-01-01' AND '2020-12-31';5. Java中的SQL实践
5.1 JDBC基础操作
String url = "jdbc:mysql://localhost:3306/company?useSSL=false"; String user = "root"; String password = "123456"; try (Connection conn = DriverManager.getConnection(url, user, password); PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM employees WHERE salary > ?")) { stmt.setDouble(1, 5000); ResultSet rs = stmt.executeQuery(); while (rs.next()) { String name = rs.getString("name"); double salary = rs.getDouble("salary"); System.out.println(name + ": " + salary); } } catch (SQLException e) { e.printStackTrace(); }5.2 事务管理
try (Connection conn = dataSource.getConnection()) { conn.setAutoCommit(false); // 开始事务 try { // 执行多个SQL操作 updateAccount(conn, fromAccount, -amount); updateAccount(conn, toAccount, amount); conn.commit(); // 提交事务 } catch (SQLException e) { conn.rollback(); // 回滚事务 throw e; } }5.3 连接池配置
以HikariCP为例:
HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/company"); config.setUsername("root"); config.setPassword("123456"); config.setMaximumPoolSize(10); config.setConnectionTimeout(30000); HikariDataSource dataSource = new HikariDataSource(config);6. 常见问题与解决方案
6.1 SQL注入防护
错误做法:
String sql = "SELECT * FROM users WHERE username = '" + username + "'";正确做法:
PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM users WHERE username = ?"); stmt.setString(1, username);6.2 连接泄露排查
典型症状:应用运行一段时间后无法获取数据库连接。
检查方法:
-- MySQL查看当前连接 SHOW PROCESSLIST;解决方案:
- 确保所有Connection、Statement、ResultSet都在try-with-resources中
- 设置合理的连接超时时间
6.3 大数据量处理
避免一次性加载大量数据到内存:
// 使用游标方式处理 stmt.setFetchSize(100); ResultSet rs = stmt.executeQuery(); while (rs.next()) { // 处理单行数据 }对于批量插入,使用addBatch():
PreparedStatement stmt = conn.prepareStatement( "INSERT INTO employees (name, email) VALUES (?, ?)"); for (Employee emp : employees) { stmt.setString(1, emp.getName()); stmt.setString(2, emp.getEmail()); stmt.addBatch(); if (i % 1000 == 0) { stmt.executeBatch(); } } stmt.executeBatch();7. 学习资源推荐
在线练习平台:
- LeetCode数据库题库
- SQLZoo
- HackerRank SQL模块
进阶书籍:
- 《SQL必知必会》
- 《高性能MySQL》
- 《SQL进阶教程》
可视化工具:
- DBeaver(开源数据库工具)
- DataGrip(JetBrains出品)
在实际项目中,我发现SQL技能的提升是一个持续的过程。每次review代码时,我都会特别关注SQL部分,思考是否有优化空间。建议定期进行SQL性能审查,这往往能发现意想不到的性能瓶颈。