1. MySQL数据操作基础
作为一名长期与MySQL打交道的开发者,我深知数据操作是数据库应用中最基础也最频繁的部分。今天我想系统梳理一下MySQL中的DML(数据操作语言)和DQL(数据查询语言)操作,分享一些实际开发中的经验和技巧。
MySQL的数据操作主要分为两大类:DML(数据操作语言)和DQL(数据查询语言)。DML负责数据的增删改,DQL负责数据的查询。这两者构成了我们对数据库表数据进行操作的基础工具集。
1.1 DML语句详解
DML全称Data Manipulation Language,主要包括INSERT、UPDATE和DELETE三种操作。我们先从最常用的INSERT开始。
1.1.1 INSERT语句的多种写法
INSERT语句用于向表中插入新记录,有几种不同的写法:
sql复制-- 指定字段名插入(推荐)
INSERT INTO 表名(字段名1, 字段名2) VALUES(值1, 值2);
-- 不指定字段名插入(需按表结构顺序)
INSERT INTO 表名 VALUES(值1, 值2);
-- 批量插入(效率更高)
INSERT INTO 表名(字段名1, 字段名2) VALUES(值1, 值2),(值3, 值4);
在实际开发中,我强烈建议使用第一种指定字段名的方式,原因有三:
- 代码可读性更强,明确知道哪个值对应哪个字段
- 表结构变更时影响更小
- 可以只插入部分字段,其他字段使用默认值
注意:批量插入时,MySQL默认每次最多插入约4MB数据(max_allowed_packet参数控制),大数据量插入时需要注意分批。
1.1.2 UPDATE语句使用技巧
UPDATE用于修改现有记录,基本语法:
sql复制UPDATE 表名 SET 字段1=值1, 字段2=值2 WHERE 条件;
这里最容易出错的就是WHERE条件。忘记加WHERE条件会导致全表更新,这是生产环境的大忌!我建议:
- 先写SELECT确认要更新的记录
- 再写UPDATE语句
- 事务中执行,便于回滚
sql复制-- 先确认
SELECT * FROM course WHERE name='数据库';
-- 再更新
UPDATE course SET score=5, time=11 WHERE name='数据库';
1.1.3 DELETE与TRUNCATE的区别
DELETE用于删除记录,TRUNCATE用于清空表,两者有重要区别:
| 特性 | DELETE | TRUNCATE |
|---|---|---|
| 条件删除 | 支持WHERE | 不支持 |
| 性能 | 逐行删除,较慢 | 直接删除表数据,很快 |
| 自增ID | 不重置 | 重置为1 |
| 可回滚 | 支持 | 不支持(大多数引擎) |
| 触发器 | 会触发 | 不会触发 |
实际应用中,小数据量删除用DELETE,大数据量清空用TRUNCATE,但要注意TRUNCATE不可逆。
1.2 DQL查询语句精要
DQL(Data Query Language)主要是SELECT语句,但它的功能非常强大。我们先看基础查询。
1.2.1 基础SELECT查询
sql复制SELECT 字段1, 字段2 FROM 表名 WHERE 条件;
几个实用技巧:
- 使用
SELECT *要谨慎,最好明确指定需要的字段 - 字段可以使用AS设置别名,提高可读性
- DISTINCT可以去除重复值
sql复制-- 好习惯:明确字段
SELECT id, name, score FROM students;
-- 使用别名
SELECT name AS 学生姓名, score AS 分数 FROM students;
-- 去重
SELECT DISTINCT department FROM teachers;
1.2.2 条件查询操作符
MySQL提供了丰富的条件操作符:
- 比较运算符:=, !=, >, <, >=, <=
- BETWEEN...AND:范围查询
- LIKE:模糊查询
- IN:多值匹配
- IS NULL/IS NOT NULL:空值判断
sql复制-- 范围查询
SELECT * FROM products WHERE price BETWEEN 10 AND 100;
-- 模糊查询
SELECT * FROM employees WHERE name LIKE '张%';
-- 多值匹配
SELECT * FROM orders WHERE status IN ('paid', 'shipped');
-- 空值检查
SELECT * FROM customers WHERE phone IS NOT NULL;
提示:LIKE查询中,%表示任意多个字符,_表示单个字符。对于大数据表,LIKE '%关键词%'会导致全表扫描,性能很差。
2. 高级查询技巧
2.1 分组与聚合函数
分组查询是数据分析的利器,结合聚合函数可以完成各种统计需求。
2.1.1 GROUP BY分组
sql复制SELECT department, COUNT(*)
FROM employees
GROUP BY department;
常见聚合函数:
- COUNT():计数
- SUM():求和
- AVG():平均值
- MAX()/MIN():最大/最小值
sql复制-- 各部门平均薪资
SELECT department, AVG(salary)
FROM employees
GROUP BY department;
-- 每月销售总额
SELECT MONTH(order_date), SUM(amount)
FROM orders
GROUP BY MONTH(order_date);
2.1.2 HAVING筛选分组结果
WHERE在分组前过滤,HAVING在分组后过滤:
sql复制-- 查询平均分大于80的班级
SELECT class, AVG(score)
FROM students
GROUP BY class
HAVING AVG(score) > 80;
2.2 排序与分页
2.2.1 ORDER BY排序
sql复制SELECT * FROM products
ORDER BY price DESC, sales ASC;
可以指定多个排序字段,每个字段可以单独指定ASC(升序)或DESC(降序)。
2.2.2 LIMIT分页
分页查询是Web应用的常见需求:
sql复制-- 第一页,每页10条
SELECT * FROM articles
ORDER BY create_time DESC
LIMIT 0, 10;
-- 第二页
SELECT * FROM articles
ORDER BY create_time DESC
LIMIT 10, 10;
现代应用更推荐使用"游标分页"方式,性能更好:
sql复制-- 假设上一页最后一条记录的ID是50
SELECT * FROM articles
WHERE id < 50
ORDER BY id DESC
LIMIT 10;
2.3 多表连接查询
实际业务中经常需要关联多张表查询数据。
2.3.1 INNER JOIN内连接
只返回两表中匹配的记录:
sql复制SELECT o.order_id, c.customer_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;
2.3.2 LEFT/RIGHT JOIN外连接
保留左表/右表的所有记录,即使没有匹配:
sql复制-- 查询所有学生及其选课(包括没选课的学生)
SELECT s.name, c.course_name
FROM students s
LEFT JOIN student_courses sc ON s.id = sc.student_id
LEFT JOIN courses c ON sc.course_id = c.id;
2.3.3 子查询
子查询可以作为临时表使用:
sql复制-- 查询成绩高于平均分的学生
SELECT name, score
FROM students
WHERE score > (SELECT AVG(score) FROM students);
3. 性能优化与常见问题
3.1 索引使用原则
- WHERE条件中的字段应该建立索引
- ORDER BY和GROUP BY字段考虑索引
- 多列条件考虑组合索引
- 避免在索引列上使用函数
sql复制-- 不好的写法(索引失效)
SELECT * FROM users WHERE YEAR(create_time) = 2023;
-- 好的写法
SELECT * FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
3.2 常见错误排查
- 字符集问题:确保连接、数据库、表的字符集一致(推荐utf8mb4)
- 大小写敏感:Linux下MySQL默认区分表名大小写
- 隐式类型转换:避免WHERE条件中类型不匹配
- 事务未提交:autocommit=0时记得COMMIT
3.3 事务使用建议
sql复制START TRANSACTION;
-- 执行多个操作
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
-- 确认无误后提交
COMMIT;
-- 出错时回滚
-- ROLLBACK;
事务使用原则:
- 尽量短小精悍
- 避免在事务中进行耗时操作
- 合理设置隔离级别
- 处理死锁问题
4. 实际应用案例
4.1 电商商品查询优化
sql复制-- 优化前的慢查询
SELECT * FROM products
WHERE category LIKE '%电子%'
ORDER BY price DESC
LIMIT 0, 20;
-- 优化后的查询
SELECT p.id, p.name, p.price, p.stock
FROM products p
INNER JOIN product_categories pc ON p.category_id = pc.id
WHERE pc.name = '电子产品'
AND p.status = 1
ORDER BY p.price DESC, p.sales DESC
LIMIT 0, 20;
优化点:
- 使用精确匹配代替LIKE
- 只查询必要字段
- 添加合理的排序条件
- 确保category_id和status有索引
4.2 数据分析报表查询
sql复制-- 月度销售报表
SELECT
DATE_FORMAT(o.order_date, '%Y-%m') AS month,
c.region,
COUNT(o.id) AS order_count,
SUM(o.amount) AS total_amount,
AVG(o.amount) AS avg_amount,
MAX(o.amount) AS max_order,
MIN(o.amount) AS min_order
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'completed'
AND o.order_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY DATE_FORMAT(o.order_date, '%Y-%m'), c.region
ORDER BY month, c.region;
这个查询展示了如何组合使用日期函数、分组、聚合和连接来生成业务报表。
4.3 批量操作优化
sql复制-- 低效的循环插入
-- 在应用程序中循环执行:
INSERT INTO log (message) VALUES ('log message');
-- 高效的批量插入
INSERT INTO log (message) VALUES
('log message 1'),
('log message 2'),
...
('log message 1000');
批量操作可以显著减少网络往返和SQL解析开销,提升性能。对于大批量数据导入,还可以考虑:
- 使用LOAD DATA INFILE(比INSERT快20-100倍)
- 临时禁用索引和约束
- 使用事务包裹批量操作
MySQL的数据操作看似简单,但要真正掌握需要大量的实践和经验积累。我在实际项目中总结的几个关键点:
- 总是先写SELECT确认要操作的数据
- 重要操作在事务中执行
- 批量操作注意分批处理
- 生产环境操作前先备份
- 复杂的查询先EXPLAIN分析执行计划
希望这些经验能帮助你在MySQL数据操作中少走弯路。记住,好的数据库操作习惯不仅能提高效率,还能避免很多潜在的问题。