尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MySQL DQL数据查询语言全解析:从基础语法到性能优化实战

MySQL DQL数据查询语言全解析:从基础语法到性能优化实战 1. 项目概述从“查”开始理解数据库的灵魂如果你刚接触数据库或者已经用了一段时间的MySQL但总觉得写查询语句时心里没底那咱们今天聊的这个话题就是为你准备的。DQL全称Data Query Language翻译过来叫数据查询语言。听起来很学术对吧但它的本质其实就是数据库世界里最核心、最频繁使用的“提问”工具。我们往数据库里存了海量的数据无论是用户信息、订单记录还是日志流水最终的目的都是为了在需要的时候能把它“查”出来。DQL就是负责这个“查”的动作。你可以把数据库想象成一个巨大的、结构严谨的图书馆。DML数据操作语言负责往书架上放新书、更新旧书的内容或者撤下不再需要的书DDL数据定义语言负责设计书架的结构、给书架分区、贴标签。而DQL呢它就是那个图书管理员或者说是你自己走到图书馆里根据你的需求——比如“我想找一本2020年之后出版的、关于人工智能的、作者姓李的书”——从浩如烟海的藏书中精准地找到你想要的那一本或那几本。没有高效的查询数据就只是一堆沉默的比特无法转化为信息和价值。所以掌握DQL绝不仅仅是学会SELECT * FROM table这么简单。它关乎效率如何在上百万条记录中毫秒级返回结果它关乎准确如何确保你拿到的数据正是业务逻辑所需要的不多也不少它更关乎你对数据本身的理解表与表如何关联数据如何聚合统计条件如何灵活组合无论是做数据分析、业务报表、后台功能开发还是解决线上慢查询问题DQL都是你绕不过去的基本功。接下来我会结合十多年的踩坑经验带你从最基础的语法骨架到高阶的性能心法彻底拆解MySQL DQL。2. DQL核心语法骨架与执行逻辑拆解所有DQL语句都始于SELECT关键字这是它的灵魂。一个完整的、功能强大的查询语句通常由多个子句Clause有机组合而成。理解每个子句的作用及其执行顺序是写出高效、正确查询的前提。很多人写了很久SQL但对执行顺序模糊不清导致遇到复杂查询时调试困难。2.1 SELECT语句的完整结构与执行顺序一个标准的SELECT查询可以包含以下子句请注意它们的书写顺序和数据库的实际执行顺序是不同的书写顺序我们写SQL时的顺序SELECT- 指定要返回的列FROM- 指定数据来源的表WHERE- 对行进行过滤GROUP BY- 将数据分组HAVING- 对分组后的结果进行过滤ORDER BY- 对结果进行排序LIMIT- 限制返回的行数执行顺序数据库引擎实际处理的顺序理解这个至关重要FROM首先确定数据来源包括表以及可能的连接JOIN。这是所有操作的基石。ON/JOIN如果存在连接根据ON条件将多张表的数据关联起来。WHERE对FROM和JOIN后产生的中间结果集进行行级过滤。WHERE子句中不能使用SELECT中定义的别名也不能使用聚合函数原因就在于它执行时SELECT列表还没被计算。GROUP BY将过滤后的数据按照指定列进行分组。HAVING对分组后的结果集进行过滤。这里可以使用聚合函数如COUNT, SUM因为分组已经完成。SELECT计算选择列表中的表达式包括普通列、聚合函数、字面量等。此时可以为列指定别名。DISTINCT如果使用了DISTINCT关键字在此阶段去除重复行。ORDER BY对最终的结果集进行排序。此处可以使用SELECT中定义的别名。LIMIT/OFFSET最后截取指定范围的记录返回给客户端。实操心得这个执行顺序是理解许多SQL“诡异”现象的金钥匙。比如为什么WHERE后面不能用SELECT的别名因为WHERE先执行。为什么HAVING能过滤聚合结果而WHERE不能因为HAVING在分组后执行。在优化查询时脑子里过一遍这个顺序你就能知道该在哪个环节加索引、哪个过滤条件更有效。2.2 SELECT子句不仅仅是SELECT *SELECT子句决定了返回结果的“形状”。SELECT *固然方便但在生产环境或性能敏感的查询中这通常是个坏习惯。列选择与别名-- 选择特定列明确所需数据 SELECT user_id, username, email FROM users; -- 使用表达式并赋予别名增强可读性 SELECT product_id, product_name, unit_price * quantity AS total_amount, -- 计算总价并别名 CONCAT(first_name, , last_name) AS full_name -- 拼接字符串并别名 FROM order_details JOIN users ON order_details.user_id users.id;注意别名AS关键字可省略在后续的ORDER BY、GROUP BY子句中可以使用但在WHERE子句中不行原因如上所述。DISTINCT去重用于返回唯一不同的值。需要注意的是DISTINCT作用于其后所有列的组合。-- 返回所有不同的部门ID SELECT DISTINCT department_id FROM employees; -- 返回(部门ID, 职位)的唯一组合 SELECT DISTINCT department_id, job_title FROM employees;对于大数据集DISTINCT操作可能比较耗时因为它需要对所有选定的列进行排序和比较。如果只是为了统计唯一值数量有时COUNT(DISTINCT column)比先SELECT DISTINCT再COUNT(*)更高效。3. 数据过滤与筛选WHERE子句的精准艺术WHERE子句是筛选数据的守门员。它的条件表达式直接决定了哪些行能进入后续的处理流程。编写高效、准确的WHERE条件是DQL的核心技能之一。3.1 基础比较与逻辑运算符除了最基础的,或!,,,,有几个运算符需要特别留意BETWEEN ... AND ...范围查询包含边界值。WHERE age BETWEEN 18 AND 30等价于WHERE age 18 AND age 30。对于日期和数字范围查询非常直观。IN (...)匹配列表中的任意值。WHERE status IN (active, pending)。当列表值很多时需要评估性能。对于大量离散值的过滤有时IN子句不如JOIN一个临时表或使用EXISTS高效。LIKE模糊匹配。%代表任意字符序列包括零个字符_代表单个字符。WHERE name LIKE 张%查找姓“张”的人。WHERE email LIKE %gmail.com查找Gmail用户。WHERE code LIKE A_B%查找以‘A’开头第三个字符是‘B’的代码。重要提示以通配符%开头的LIKE查询如LIKE %keyword通常无法使用索引会导致全表扫描在大表上性能极差。如果业务上必须进行后缀匹配考虑使用全文索引FULLTEXT或专门的搜索引擎。3.2 处理NULL值三值逻辑的陷阱NULL在SQL中代表“未知”或“不存在”它不等于任何值甚至不等于它自己。这是新手最容易踩坑的地方之一。-- 错误这不会返回phone为NULL的行 SELECT * FROM customers WHERE phone NULL; -- 正确做法使用 IS NULL 或 IS NOT NULL SELECT * FROM customers WHERE phone IS NULL; SELECT * FROM customers WHERE phone IS NOT NULL;在条件组合中NULL会导致逻辑复杂化。例如WHERE NOT (age 20)对于age为NULL的行结果也是NULL未知不会被WHERE子句选中因为WHERE只接受条件为TRUE的行。3.3 条件组合与优先级使用AND、OR和括号()来组合复杂条件。-- 查找状态为活跃且要么是VIP要么消费金额大于1000的用户 SELECT * FROM users WHERE status active AND (is_vip TRUE OR total_spent 1000);运算符优先级NOTANDOR。如果不确定或者为了代码清晰强烈建议使用括号来明确指定条件组合的逻辑避免出现非预期的结果。4. 数据排序与分页ORDER BY与LIMIT的实战要点查询结果默认以数据在表中存储的物理顺序返回这是不确定的。要获得有序、可控的结果必须使用ORDER BY和LIMIT。4.1 ORDER BY多列排序与自定义排序-- 单列排序按创建时间降序最新的在前 SELECT * FROM articles ORDER BY created_at DESC; -- 多列排序先按类别升序同类中再按价格降序 SELECT * FROM products ORDER BY category ASC, price DESC; -- 使用表达式或函数排序按名字长度排序 SELECT * FROM users ORDER BY LENGTH(username) DESC; -- 使用FIELD函数自定义排序规则让特定状态按指定顺序出现 SELECT * FROM orders ORDER BY FIELD(status, urgent, high, normal, low) ASC, order_date DESC;性能注意ORDER BY子句尤其是对非索引列的排序或对多列进行不同方向的排序如col1 ASC, col2 DESC可能需要在内存或磁盘上进行排序操作Using filesort对于大结果集非常消耗资源。如果排序是高频操作考虑在相关列上建立复合索引。4.2 LIMIT与分页高效与深分页难题LIMIT用于限制返回的行数常与OFFSET结合用于分页。-- 获取前10条记录 SELECT * FROM logs LIMIT 10; -- 经典分页获取第3页每页20条跳过前40条取20条 SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 40; -- 等价写法 SELECT * FROM products ORDER BY id LIMIT 40, 20;深分页的性能陷阱LIMIT 10000, 20这样的查询MySQL需要先扫描并排序如果用了ORDER BY前10020条记录然后丢弃前10000条返回最后的20条。当OFFSET值非常大时这个“扫描-丢弃”的过程会异常缓慢。解决方案基于游标的分页Cursor-based Pagination不使用OFFSET而是记录上一页最后一条记录的某个唯一、有序的字段值如自增ID、时间戳下一页查询时使用WHERE id last_id。这要求排序字段是唯一的。-- 第一页 SELECT * FROM items ORDER BY created_at DESC, id DESC LIMIT 20; -- 假设最后一条记录的created_at为 2023-10-01 12:00:00, id为 12345 -- 第二页 SELECT * FROM items WHERE (created_at 2023-10-01 12:00:00) OR (created_at 2023-10-01 12:00:00 AND id 12345) ORDER BY created_at DESC, id DESC LIMIT 20;覆盖索引优化确保ORDER BY和WHERE用到的列都在一个索引中这样MySQL可以仅通过扫描索引就完成排序和过滤避免回表查询数据行即使有OFFSET也能快很多。业务妥协限制用户可翻页的深度或提供基于筛选条件如日期范围的跳转而非简单的“上一页/下一页”。5. 数据聚合与分组GROUP BY与聚合函数当我们需要汇总数据而不是查看每一条明细时就需要用到聚合Aggregation。常见的聚合函数包括COUNT(): 计数。COUNT(*)统计行数COUNT(column)统计该列非NULL值的数量。SUM(): 求和。AVG(): 求平均值。MAX()/MIN(): 求最大/最小值。GROUP_CONCAT(): 将同一组内的字符串连接起来MySQL特有。5.1 GROUP BY的基本使用GROUP BY将数据行按指定列分成不同的组聚合函数则对每个组进行计算。-- 统计每个部门的员工数量 SELECT department_id, COUNT(*) AS employee_count FROM employees GROUP BY department_id; -- 统计每个类别产品的平均价格和最高价格 SELECT category, AVG(price) AS avg_price, MAX(price) AS max_price, COUNT(*) AS product_count FROM products WHERE is_active TRUE GROUP BY category;关键规则SELECT列表中所有未被聚合函数包裹的列必须出现在GROUP BY子句中。这是因为对于每个分组数据库需要知道如何展示这些列。如果一列在分组后有多条不同值数据库无法确定该显示哪一个。5.2 HAVING分组后的过滤WHERE在分组前过滤行HAVING在分组后过滤组。-- 找出订单总金额超过10000的客户 SELECT customer_id, SUM(amount) AS total_amount FROM orders WHERE status completed -- 先过滤掉未完成的订单 GROUP BY customer_id HAVING total_amount 10000; -- 再过滤掉总金额不达标的客户组 -- 找出发布文章超过5篇的用户 SELECT author_id, COUNT(*) AS article_count FROM articles GROUP BY author_id HAVING article_count 5;经验之谈尽量将过滤条件写在WHERE子句而不是HAVING。因为WHERE在分组前执行可以减少需要分组和处理的数据量性能更好。HAVING应仅用于那些必须基于聚合结果进行过滤的条件。5.3 WITH ROLLUP生成小计与总计WITH ROLLUP是GROUP BY的一个扩展它会在分组结果的基础上生成层次性的小计和总计行。-- 按年份和季度统计销售额并生成季度小计和年度总计 SELECT YEAR(order_date) AS order_year, QUARTER(order_date) AS order_quarter, SUM(amount) AS total_sales FROM orders GROUP BY order_year, order_quarter WITH ROLLUP;结果中order_quarter为NULL的行表示该order_year的季度小计order_year和order_quarter都为NULL的行表示所有年份的总计。这个功能在做报表时非常有用。6. 多表关联查询JOIN的深入解析现实中的数据很少只存在于一张表中。JOIN操作允许我们根据相关列将多个表中的数据组合起来。理解不同类型的JOIN及其应用场景是关键。6.1 JOIN的类型与维恩图理解INNER JOIN内连接返回两个表中连接条件匹配的行。这是最常用、默认的JOIN类型。SELECT u.username, o.order_no, o.amount FROM users u INNER JOIN orders o ON u.id o.user_id;LEFT (OUTER) JOIN左外连接返回左表的所有行即使右表中没有匹配的行。如果右表无匹配则结果集中右表的部分用NULL填充。-- 列出所有用户及其订单即使该用户没有订单 SELECT u.username, o.order_no FROM users u LEFT JOIN orders o ON u.id o.user_id;RIGHT (OUTER) JOIN右外连接与左连接相反返回右表的所有行。实践中使用较少因为通常可以通过调换表顺序用LEFT JOIN实现。FULL (OUTER) JOIN全外连接返回左右两表的所有行。当某一行在另一表中没有匹配时另一表的部分用NULL填充。MySQL原生不支持FULL JOIN但可以通过LEFT JOIN和RIGHT JOIN的UNION来模拟。CROSS JOIN交叉连接返回两表的笛卡尔积每一行都与另一表的每一行组合。通常需要与WHERE条件结合使用否则结果集巨大。6.2 JOIN的性能考量与索引JOIN的性能很大程度上取决于连接条件列上是否有索引。通常应该在连接条件ON子句的列上建立索引。驱动表选择在INNER JOIN中MySQL查询优化器会尝试选择数据量较小的表作为驱动表最先被读取的表然后去另一张表被驱动表中查找匹配行。确保被驱动表的连接列上有索引否则会导致全表扫描Nested Loop Join without index性能灾难。复合索引如果连接条件涉及多个列考虑建立复合索引。例如ON table_a.col1 table_b.col1 AND table_a.col2 table_b.col2在(table_b.col1, table_b.col2)上建立复合索引会更高效。避免不必要的JOIN有时可以通过子查询或应用程序中分两次查询来替代复杂的多表JOIN尤其是当JOIN导致中间结果集急剧膨胀时。6.3 自连接与多表连接自连接Self Join将一张表与自己连接常用于处理层次结构数据如员工-经理关系或比较同一表内的行。-- 查找每个员工及其经理的名字 SELECT e.emp_name AS employee_name, m.emp_name AS manager_name FROM employees e LEFT JOIN employees m ON e.manager_id m.emp_id;多表连接可以连接超过两张表。书写时建议使用清晰的别名并按逻辑顺序排列。SELECT u.username, o.order_no, p.product_name, od.quantity FROM users u INNER JOIN orders o ON u.id o.user_id INNER JOIN order_details od ON o.id od.order_id INNER JOIN products p ON od.product_id p.id WHERE o.status shipped;7. 子查询嵌套查询的灵活运用子查询Subquery是嵌套在另一个查询主查询内部的查询。它非常灵活可以出现在SELECT、FROM、WHERE、HAVING等子句中。7.1 标量子查询与行子查询标量子查询返回单个值一行一列的子查询。常用在WHERE或SELECT列表中与比较运算符,,等一起使用。-- 找出工资高于平均工资的员工 SELECT emp_name, salary FROM employees WHERE salary (SELECT AVG(salary) FROM employees); -- 在SELECT列表中使用标量子查询可能影响性能需谨慎 SELECT order_id, amount, (SELECT username FROM users WHERE id orders.user_id) AS username FROM orders;行子查询返回单行但可能有多列的子查询。较少使用。SELECT * FROM t1 WHERE (col1, col2) (SELECT col3, col4 FROM t2 WHERE id 10);7.2 列子查询与表子查询列子查询返回单列多行的子查询。通常与IN、ANY/SOME、ALL操作符一起使用。-- 找出有订单的所有用户 SELECT * FROM users WHERE id IN (SELECT DISTINCT user_id FROM orders); -- 找出比部门内任何一个人工资都高的员工使用 ANY SELECT emp_name, salary, department_id FROM employees e1 WHERE salary ANY (SELECT salary FROM employees e2 WHERE e2.department_id e1.department_id AND e2.emp_id e1.emp_id);表子查询返回一个虚拟表多行多列的子查询。通常用在FROM子句中必须为其指定别名。-- 将子查询结果作为临时表进行连接 SELECT d.dept_name, emp_stats.avg_sal FROM departments d JOIN (SELECT department_id, AVG(salary) AS avg_sal FROM employees GROUP BY department_id) AS emp_stats ON d.id emp_stats.department_id;7.3 相关子查询与非相关子查询非相关子查询子查询可以独立执行不依赖于外部查询。如上面的(SELECT AVG(salary) FROM employees)。相关子查询子查询的执行依赖于外部查询的当前行值。通常性能较差因为需要对外部查询的每一行都执行一次子查询。-- 找出每个部门工资最高的员工 SELECT emp_name, salary, department_id FROM employees e1 WHERE salary (SELECT MAX(salary) FROM employees e2 WHERE e2.department_id e1.department_id);优化建议很多相关子查询可以用JOIN配合GROUP BY来重写性能往往更好。上面的例子可以改写为SELECT e.emp_name, e.salary, e.department_id FROM employees e INNER JOIN (SELECT department_id, MAX(salary) AS max_sal FROM employees GROUP BY department_id) dept_max ON e.department_id dept_max.department_id AND e.salary dept_max.max_sal;8. 组合查询UNION的合并之道UNION操作符用于合并两个或多个SELECT语句的结果集。要求每个SELECT语句必须拥有相同数量的列且列的数据类型必须兼容。UNION默认去除重复行。UNION ALL保留所有行包括重复行。性能通常比UNION好因为不需要进行去重操作。-- 从两个不同的表中合并活跃用户列表 SELECT user_id, customer AS type FROM customers WHERE status active UNION ALL SELECT user_id, admin AS type FROM admins WHERE is_active 1 ORDER BY user_id; -- ORDER BY 作用于整个合并后的结果集使用场景合并来自不同表或视图的相似数据。将复杂的OR条件拆分成多个UNION查询有时可以利用不同的索引提升性能。例如对WHERE col A OR col B如果col上有索引优化器可能选择全表扫描。而写成WHERE col A UNION ALL WHERE col B则可能分别利用索引进行两次查找再合并结果。9. 窗口函数高级分析与排名利器窗口函数Window Function是MySQL 8.0引入的强大特性。它允许你对一组相关的行一个“窗口”进行计算而不像GROUP BY那样将多行聚合成一行。每行都保留其原始细节同时附加了计算结果。9.1 核心语法与OVER()子句窗口函数的核心是OVER()子句它定义了窗口的范围。function_name([expression]) OVER ( [PARTITION BY partition_expression, ... ] [ORDER BY sort_expression [ASC | DESC], ... ] [frame_clause] )PARTITION BY将结果集划分为多个分区窗口函数在每个分区内独立计算。类似于GROUP BY的分组但行不会被合并。ORDER BY定义分区内的排序方式。对于排名类函数ROW_NUMBER,RANK是必须的对于聚合类窗口函数它决定了计算累加值时的顺序。frame_clause定义当前行周围的一个滑动窗口用于计算移动平均等。例如ROWS BETWEEN 2 PRECEDING AND CURRENT ROW。9.2 常用窗口函数示例排名函数-- 为每个部门的员工按工资排名 SELECT emp_name, department_id, salary, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank_within_dept, RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank_with_ties, DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dense_rank_with_ties FROM employees;ROW_NUMBER()连续排名即使值相同排名也不同。RANK()排名值相同则排名相同但会跳过后续排名如 1,2,2,4。DENSE_RANK()密集排名值相同则排名相同且不跳过排名如 1,2,2,3。聚合窗口函数-- 计算每个员工的工资、部门平均工资、部门内累计工资 SELECT emp_name, department_id, salary, AVG(salary) OVER (PARTITION BY department_id) AS dept_avg_salary, SUM(salary) OVER (PARTITION BY department_id ORDER BY hire_date) AS running_total_in_dept, SUM(salary) OVER (PARTITION BY department_id) AS dept_total_salary FROM employees;取值函数-- 获取每个订单的上一笔订单金额和下一笔订单金额按时间排序 SELECT order_id, order_date, amount, LAG(amount, 1) OVER (ORDER BY order_date) AS prev_order_amount, LEAD(amount, 1) OVER (ORDER BY order_date) AS next_order_amount FROM orders;LAG(column, n)获取当前行之前第n行的值。LEAD(column, n)获取当前行之后第n行的值。窗口函数极大地简化了复杂分析查询的编写是进行数据对比、趋势分析、排名计算的终极武器。10. 常见问题与排查技巧实录在实际使用DQL的过程中你会遇到各种各样的问题。这里记录了一些典型场景和排查思路。10.1 查询结果不符合预期NULL值处理不当这是最常见的原因之一。检查WHERE条件中是否错误地使用了或!来比较NULL。牢记使用IS NULL或IS NOT NULL。JOIN类型用错你想要所有用户但用了INNER JOIN结果只返回了有订单的用户。确认你需要的逻辑是“内连接”、“左连接”还是其他。GROUP BY与SELECT列不匹配在ONLY_FULL_GROUP_BYSQL模式下MySQL 5.7后默认SELECT列表中非聚合列必须出现在GROUP BY中否则报错。检查错误信息。条件逻辑优先级复杂的AND/OR组合可能因优先级产生歧义。养成使用括号明确优先级的好习惯。字符集和排序规则问题比较字符串时如果两列的字符集或排序规则不同可能导致比较失败或结果异常。使用SHOW FULL COLUMNS FROM table_name;检查列的定义。10.2 查询性能慢如蜗牛检查执行计划EXPLAIN这是性能调优的第一步。在查询前加上EXPLAIN或EXPLAIN FORMATJSON查看MySQL打算如何执行这条语句。关注type列ALL全表扫描通常最差index全索引扫描次之ref/eq_ref/const索引查找较好。关注key列实际使用的索引。关注rows列预估需要扫描的行数。关注Extra列Using filesort需要额外排序、Using temporary需要创建临时表都是性能红灯。索引缺失或失效WHERE、JOIN ON、ORDER BY、GROUP BY子句中的列是索引的候选。避免在索引列上使用函数或计算如WHERE YEAR(create_time) 2023这会导致索引失效。应改为范围查询WHERE create_time 2023-01-01 AND create_time 2024-01-01。避免使用前导通配符的LIKE查询。使用复合索引时注意最左前缀原则。返回过多数据检查是否真的需要SELECT *是否可以使用LIMIT应用程序是否一次性获取了远超需要的数据量复杂的子查询或视图尝试将相关子查询重写为JOIN或将复杂查询拆分成多个简单查询在应用层组合。锁竞争在并发高的系统中查询可能因为等待行锁、表锁而变慢。可以观察SHOW ENGINE INNODB STATUS中的锁信息。10.3 特定错误与解决方法错误/现象可能原因排查与解决思路ERROR 1055在ONLY_FULL_GROUP_BY模式下SELECT列表中的非聚合列未在GROUP BY中出现。1. 修改查询将非聚合列添加到GROUP BY中。2. 使用ANY_VALUE()函数包裹非聚合列如果你确定该列在组内值都相同。3. 临时修改会话的SQL模式不推荐长期使用SET SESSION sql_mode(SELECT REPLACE(sql_mode,ONLY_FULL_GROUP_BY,));Using filesortORDER BY或GROUP BY的列无法利用索引进行排序。1. 为ORDER BY/GROUP BY的列创建索引。2. 如果ORDER BY和WHERE条件涉及不同列考虑创建复合索引将WHERE条件的列放在前面ORDER BY的列放在后面。Using temporary查询需要创建临时表来处理常见于复杂的GROUP BY、DISTINCT、UNION等操作。1. 优化查询减少数据中间集的大小。2. 确保GROUP BY的列上有索引。3. 适当调大tmp_table_size和max_heap_table_size参数如果临时表在内存中创建。结果集顺序随机未使用ORDER BY结果顺序依赖于存储引擎和查询计划是不确定的。始终为需要确定顺序的查询加上ORDER BY子句。即使当前看起来有序数据变更或版本升级后顺序也可能改变。分页越来越慢使用了大OFFSET的LIMIT查询。采用“基于游标的分页”或“覆盖索引”进行优化具体方法见第4.2节。掌握DQL就是掌握了从数据海洋中精准捕捞所需信息的渔网。从最基础的SELECT到复杂的窗口函数每一层理解都能让你在数据处理上更加游刃有余。记住写出能跑的SQL只是第一步写出高效、清晰、易于维护的SQL才是我们追求的目标。多实践多使用EXPLAIN分析多总结踩过的坑你的查询技能自然会不断提升。
返回列表