恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

MySQL DML与DQL核心语法详解:从增删改到高性能查询优化

  • 首页
  • 资讯中心
  • /
  • MySQL DML与DQL核心语法详解:从增删改到高性能查询优化

相关资讯

多智能体系统为何越多越需要总调度?踩坑实践与架构设计 2026/10/11 21:23:26
天龙八部服务端源码部署实战:从编译到启动的完整避坑指南 2026/10/11 21:23:26
AI数据分析Agent企业内网安全落地:数据分级、权限与脱敏实战 2026/10/11 21:23:26

最新资讯

向量数据库与图数据库协同:构建智能问答系统的混合检索架构
树形DP从暴力到换根:P15533那友谊连成的树95分思维
基于Python标准库的跨平台临时文件清理工具开发实战
Hermes Agent中文工作流实战:7个可落地的办公自动化方案
风筝检测数据集2260张VOC+YOLO格式:YOLOv8训练与避坑指南
天鹰算法优化GRNN平滑因子:预测模型智能调参实战

今日推荐

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

MySQL DML与DQL核心语法详解:从增删改到高性能查询优化

发布时间:2026/10/11 21:23:26
MySQL DML与DQL核心语法详解:从增删改到高性能查询优化 SQL写得好不好直接决定你下班早不早。这句话在业务开发里真不是开玩笑。我见过不少同事CRUD写了大半年结果一条带两三个JOIN的分页查询能把数据库查卡壳也见过有人因为一条UPDATE没加WHERE直接干翻整个线上配置表。今天这篇笔记就是把DML数据操作语言和DQL数据查询语言这两块的核心语法掰开揉碎讲透结合我自己实际写过的业务查询给你整理一套可以直接照着抄的写法顺手把那些“当时想不通、事后才明白”的坑也一并交代清楚。适合刚学MySQL不久的同学也适合写SQL总是磕磕绊绊、想在语法细节上查漏补缺的朋友。1. 先把DML和DQL这盘棋摆清楚1.1 SQL语句家族的分工说到DML和DQL就绕不开SQL语言的完整分类。标准SQL按功能划分主要分成四大家族DDL数据定义语言、DML数据操作语言、DQL数据查询语言和DCL数据控制语言。搞混这四类是很多新手最开始犯的错误比如有人问我“CREATE TABLE算是DML吗”这其实就是称呼上的认知没建立好。从实用性上来讲DML和DQL才是日常开发里的主角。DML管的是“增、删、改”对应三条核心语句INSERT、UPDATE、DELETE。DQL管的是“查”核心语句就是SELECT。你可能注意到DML里没有把SELECT塞进来这是因为SQL标准把查询单独拎成了一个类别虽然很多时候我们习惯把“增删改查”合在一起说成“CRUD”。你别小看这个分类它直接影响你后面理解事务、锁、日志这些东西。比如INSERT、UPDATE、DELETE会触发事务日志记录和行锁而SELECT在默认隔离级别下走的是快照读这两类操作的底子完全不一样。为了让你有个整体印象我列个对比表分类全称核心语句日常作用DDLData Definition LanguageCREATE、ALTER、DROP定义表结构、修改表结构DMLData Manipulation LanguageINSERT、UPDATE、DELETE操作表中的数据内容DQLData Query LanguageSELECT查询表中的数据内容DCLData Control LanguageGRANT、REVOKE权限控制和管理这里有个经常被忽略的点TRUNCATE TABLE虽然在效果上是清空表数据但它属于DDL不是DML。这一点在权限管理和事务回滚上差异巨大。TRUNCATE执行时不会逐行触发删除而是直接释放数据页所以在事务里用TRUNCATE想回滚很多时候是回不来的这就是它和DELETE最大的本质区别后面我会专门展开。1.2 DML与DQL的配合思路实际业务里DML和DQL从来不是孤立使用的。你要先查出来哪些数据有问题然后才能去更新你要先插入一批数据然后才能统计查询。所以学习的时候建议你脑子里带着“查询是基础操作是目的”这条主线。我举个例子一个订单系统里运营同事要求把“近30天未支付且超过2小时的订单”标记为超时关闭。这个需求听起来是个更新动作用DML写UPDATE但UPDATE的条件列表里满眼都是DQL的逻辑——WHERE create_time ... AND status UNPAID。你如果查询底子不牢条件里的时间函数、逻辑运算符、子查询全都写不顺那这条更新语句写出来就是生产事故预定的节奏。所以我这篇笔记的顺序也很简单先讲DML怎么安全地“动数据”再讲DQL怎么高效地“取数据”最后把两者结合到实际场景里去解决问题。重点放在语法背后的使用边界和执行逻辑上而不是光罗列晦涩的标准条款。2. DML核心语法增删改的实操要点2.1 INSERT插入数据时没你想的那么简单INSERT是DML里最基础的动作语法长得也很老实-- 最简单的单行插入 INSERT INTO user (name, age, email) VALUES (张三, 25, zhangsanexample.com); -- 多行插入 INSERT INTO user (name, age, email) VALUES (李四, 30, lisiexample.com), (王五, 28, wangwuexample.com), (赵六, 35, zhaoliuexample.com);这里第一个技巧是线上批量插入千万别一行一行写INSERT一次性能塞就一次性塞能多行就多行。MySQL客户端和服务端之间有网络往返你每执行一条INSERT就多一次RTT往返时延1万条数据单条插入和1万条数据分500批插入性能差距不是一个量级。实测下来批量插入的时间往往能缩小到单条插入的十分之一甚至更低这也是日常开发中优化数据导入效率最直接的套路。第二个容易被忽视的点是INSERT INTO ... SELECT这种写法INSERT INTO user_bak (id, name, age, email) SELECT id, name, age, email FROM user WHERE age 30;这种“从查询结果直接灌入表”的语法在数据归档、临时表拆分的场景里极其好用。你要做数据迁移、备份抽数时它就是第一选择。不过要注意INSERT INTO ... SELECT在执行时会持有源表相关的锁如果是线上业务高峰期跑大批量归档可能引发源表的锁等待。我一般会建议放在凌晨低峰期跑或者分批加LIMIT限制处理量。再提一个MySQL特有的扩展叫INSERT ... ON DUPLICATE KEY UPDATE很实战INSERT INTO user (id, name, age) VALUES (1001, 张三, 26) ON DUPLICATE KEY UPDATE age VALUES(age);它解决的是“这行数据存在就更新不存在就插入”这种需求比如每天的统计汇总表某天已经跑过任务了今天重跑就覆盖昨天的数据。用这个语法你再也不用先SELECT判断有没有记录再去决定UPDATE还是INSERT。省去一次查询还避免了并发下两个连接同时判断“不存在”然后双双插入的竞态问题。这里有个版本上的坑要提醒MySQL 8.0.20 之后VALUES(age)这种写法被标记为废弃官方推荐用AS别名新语法INSERT INTO user (id, name, age) VALUES (1001, 张三, 26) AS new ON DUPLICATE KEY UPDATE age new.age;虽然老项目里旧写法还能跑但新项目建议直接按新标准写免得日后版本升上来报一堆warning。2.2 UPDATE更新之前先问自己三句话UPDATE是DML里最危险的操作没有之一。语法本身很简单UPDATE user SET age 26 WHERE name 张三;但越简单越容易出事。常规开发中执行UPDATE前我建议你强制自己过三句话第一条件列有没有索引第二是否已经先SELECT确认了影响行数第三有没有开启事务或者备份再动手先说说索引的问题。UPDATE的条件列如果没有索引执行的时候就是全表扫描。数据量大起来一条更新能把整个库的CPU跑满。举个例子你在一个100万行的表上执行UPDATE user SET status 1 WHERE created_date 2024-01-01如果created_date没有索引这个更新的代价和遍历全表没区别。所以条件列上是否建索引直接影响更新效率和锁范围。然后是生产事故高发区没写WHERE条件的更新。比如UPDATE user SET age 18;这行一执行全表的人岁数都变成18了。现实里这种事故一点不少见忘记在编辑器里注释掉条件、手滑删了条件、写条件的时候表单没传参直接拼出空串……我自己的习惯是凡是要UPDATE或者DELETE的SQL有条件的先写成WHERE 1 1 AND ...这样即使后面的条件拼接出问题也不会直接变成一个全表操作。UPDATE还有一个细节点多表关联更新。比如要“根据订单表的支付状态更新用户表的会员等级”。MySQL支持这种写法UPDATE user u JOIN order o ON u.id o.user_id SET u.level VIP WHERE o.pay_status 1 AND o.create_time 2024-06-01;这种关联更新的好处是逻辑清晰不用先查订单再回写。但要注意JOIN更新时如果JOIN后一个用户匹配到多条订单记录这个用户可能被更新多次。你得确保关联关系是“一对一”或者“多对一”否则结果容易出乎意料。稳妥做法是先用SELECT版本查一下JOIN产生的行数有没有重复再转成UPDATE语句执行。2.3 DELETE与TRUNCATE删数据要学会留后路DELETE的语法同样一眼就能看懂DELETE FROM user WHERE id 1001;它的本质是逐行删除每一行删除动作都会记录到事务日志里所以它是可以回滚的——前提是你把它包在一个事务里。实际开发里线上大规模删除数据我不建议一口气跑完而是分批删DELETE FROM user WHERE age 10 ORDER BY id LIMIT 1000;然后通过脚本循环执行几千次每次删除后停顿一小会儿。这样做的原因很简单一次删太多行事务日志膨胀会非常快锁的持有时间也很长直接拖垮同表其他业务的读写。分批删除相当于把一次大事务拆成一堆小事务每条SQL执行时间短锁释放及时主从延迟也会平滑很多。再来看TRUNCATETRUNCATE TABLE user_bak;它是直接把整个表的“数据页”清空效率极高但它属于DDL不走行级日志事务里基本不能回滚而且会重置自增主键。需要特别强调的是TRUNCATE在MySQL里执行时会对表加DDL锁这个锁的粒度比行锁重得多线上热表上随手TRUNCATE可能会把其他线程堵死。所以它只适合清空非核心的临时表、备份表这种场景。谈到删除还有个“软删除”的实践业务表加一个deleted字段删除时做UPDATE deleted 1查询时默认过滤WHERE deleted 0。这个方案在多数业务系统里都值得推行。真删数据在运维上成本很高恢复数据的过程不仅烦琐还容易背上“数据完整性”的锅而软删除给所有记录留了一扇回头门。代价是每个查询都要记得过滤这属于很划算的取舍。2.4 DML与事务的黄金拍档DML操作天然和事务绑定在一起。MySQL默认事务自动提交意思是你执行一条INSERT、UPDATE、DELETE它自己就独立成一个事务提交掉了。但在复杂业务里往往需要多步DML一起成功或一起失败这时候就要手动开启事务START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT; -- 如果中间某一步出错则执行 ROLLBACK;用事务包裹DML操作最大的价值是给数据一致性兜底。比如说转账扣款成功了、加款失败了如果没有事务这两条数据就永远错位了。而事务里一旦发生错误一个ROLLBACK就能把前面所有变化撤干净。这里强调一个小技巧写事务时不要等到最后才判断错误代码里应该每个关键步骤判断影响行数不满足预期就立刻ROLLBACK。比如扣款后检查balance有没有变成负数变成负数意味着余额不足这种业务规则属于事务内校验必须在COMMIT前拦截掉。关于事务隔离级别业务开发里最常用的是REPEATABLE READ可重复读MySQL默认和READ COMMITTED读已提交。两者的区别体现在并发场景下读到的东西不同。写DML时你要知道自己的事务在并发环境里可能碰到的情况REPEATABLE READ下同一个事务里两次SELECT结果保持一致这对报表类逻辑很重要READ COMMITTED则可能读到其他事务新提交的数据适合实时性要求高的查询。具体怎么选看业务对“读一致”的容忍度没有绝对的好坏。3. DQL查询基本功SELECT的完整面貌3.1 SELECT语法骨架与执行顺序SELECT是DQL的核心也是你每天写SQL接触最多的语法。一个完整的查询语句各子句的书写顺序如下SELECT column1, column2, aggregate_function(column3) FROM table_name WHERE filter_condition GROUP BY column1, column2 HAVING group_filter_condition ORDER BY column1 ASC/DESC LIMIT offset, row_count;书写顺序是死板的但执行顺序才是理解SQL的钥匙。从执行引擎的角度看SQL真正跑起来的顺序是这样的FROM确定数据源加载表。WHERE对每一行做条件过滤过滤掉不满足条件的行。GROUP BY把过滤后的行按指定列分组。HAVING对分组后的结果继续过滤主要过滤聚合结果。SELECT计算目标列包括别名、表达式、聚合函数等。ORDER BY对最终结果排序。LIMIT截取行数。理解执行顺序的最大好处是你能解释清楚“为什么WHERE里不能直接使用SELECT里定义的别名”。比如SELECT age AS a FROM user WHERE a 18;这条在MySQL里是错的。因为WHERE的执行顺序在SELECT之前这时候别名a还不存在。你要过滤年龄列就得老老实实写成WHERE age 18。同理HAVING是在GROUP BY之后执行的所以HAVING里可以使用聚合函数而WHERE不行。这个执行顺序表建议每个写SQL的人背下来它能帮你排查掉八成“看起来语法没问题但就是报错”的怪案。3.2 WHERE条件过滤与运算符细节WHERE是用来定位数据的。它的运算方式无非就是比较、逻辑、范围、模糊匹配这几类-- 比较运算符 SELECT * FROM user WHERE age 18; SELECT * FROM user WHERE name ! 张三; -- 范围比较 SELECT * FROM user WHERE age BETWEEN 18 AND 30; SELECT * FROM user WHERE id IN (1, 2, 3); SELECT * FROM user WHERE create_time 2024-06-01 AND create_time 2024-07-01; -- 模糊匹配 SELECT * FROM user WHERE name LIKE 张%;这里有几个很常见的坑。第一个是NULL值的比较。写过WHERE name NULL的人请自己举手。在SQL里NULL表示“未知”任何与NULL做、、比较的结果都是“未知”也就是不为真。所以判断某个字段是否为空要用IS NULL或IS NOT NULLSELECT * FROM user WHERE email IS NULL; SELECT * FROM user WHERE email IS NOT NULL;这个点说起来简单但实际开发里翻车率极高。尤其是在统计场景一个NULL悄悄混进WHERE条件结果可能少好几行你还查不出原因。第二个坑是LIKE模糊查询的索引失效问题。LIKE 张%是前缀匹配索引通常能用LIKE %张和LIKE %张%都是全文本扫描级别的查询有索引也没用。我见过有人在一个百万级用户名表上做搜索写的是WHERE name LIKE %关键词%结果每次查询都要扫全表。这种场景的正确解法是小数据量就忍着全表扫数据量大了要么上全文索引要么引入专门的搜索中间件把查询压力从数据库里移出去。这是架构层面的事但写SQL的人至少要知道根因在哪。第三个坑是函数包裹字段会让索引失效。比如把日期字段用函数格式化后再比较SELECT * FROM order WHERE DATE_FORMAT(create_time, %Y-%m-%d) 2024-06-01;这么写意味着数据库没法利用create_time上的索引因为每一行都要先算一遍DATE_FORMAT再去比较。正确姿势是直接写成范围比较SELECT * FROM order WHERE create_time 2024-06-01 00:00:00 AND create_time 2024-06-02 00:00:00;这种等值换算的写法既保留了结果含义又能吃上索引是SQL优化中最基础但最实用的一招。3.3 排序和分页ORDER BY与LIMIT排序和分页是列表类查询的标配。ORDER BY语法没什么好说的默认升序加DESC变降序多列排序就逗号分隔SELECT * FROM order WHERE user_id 88 ORDER BY create_time DESC, id DESC LIMIT 0, 20;这里要说两个细节。第一多列排序时排序优先级是从左到右先按create_time排相同再按id排。第二分页有个经典问题LIMIT后的大偏移量查询会非常慢。比如LIMIT 100000, 20数据库要先扫描出前面10万行然后跳过再返回最后20行这10万行的扫描成本就是把查询拖垮的元凶。怎么优化深分页有两个思路。思路一是“用子查询的方式先定位主键再回表取数据”SELECT * FROM order WHERE id (SELECT id FROM order ORDER BY create_time DESC, id DESC LIMIT 100000, 1) ORDER BY create_time DESC, id DESC LIMIT 20;思路二是“记住上一页的最后一条记录游标”。比如上一页最后一条的id 998800下一页直接查SELECT * FROM order WHERE id 998800 ORDER BY id DESC LIMIT 20;这种“游标分页”方案在数据不断增长的业务场景里比传统LIMIT翻页稳定得多App上的“加载更多”基本都是这么干的。代价是它不支持随意跳页但实际产品里绝大多数用户也就看个前几十条跳页需求并没有那么强。4. 聚合与分组从数据里提炼结论4.1 聚合函数常用全家桶聚合函数是DQL里用来做统计分析的利器。常用的有这么几个-- 计数 SELECT COUNT(*) FROM user; SELECT COUNT(1) FROM user; SELECT COUNT(email) FROM user; -- 只统计email非NULL的行数 -- 求和、平均、最大、最小 SELECT SUM(amount) AS total_amount, AVG(amount) AS avg_amount, MAX(amount) AS max_amount, MIN(amount) AS min_amount FROM order WHERE pay_status 1;关于COUNT(*)、COUNT(1)、COUNT(列名)的差异我在面试和工作中被问过很多次。结论是COUNT(*)和COUNT(1)在实际执行计划里基本没有区别都是统计所有行数COUNT(列名)则只统计该列非NULL的行数。最常见的坑是你想统计“有多少用户填了邮箱”但用了SELECT COUNT(*) FROM user然后发现数字对不上其实你是想COUNT(email)。这个语义差异必须刻在脑子里。聚合函数还有一个天然特性如果和普通字段混在一起查MySQL在ONLY_FULL_GROUP_BY模式下会直接报错这是MySQL 5.7以后默认开启的模式。所以写分组查询的时候SELECT出来的非聚合列必须出现在GROUP BY后面否则跑都跑不起来。4.2 GROUP BY分组与HAVING过滤GROUP BY的作用是把相同值的行归并到一起然后对每组做聚合。典型用法如下SELECT status, COUNT(*) AS cnt FROM order GROUP BY status;这能统计出每个状态下的订单数量是运营报表里的标配。在此基础上如果你想过滤“订单数大于100的状态”不能把过滤条件放在WHERE里因为WHERE在分组前执行它没法感知聚合后的统计结果。正确姿势是SELECT status, COUNT(*) AS cnt FROM order GROUP BY status HAVING cnt 100;HAVING和WHERE的边界我再用执行顺序帮你捋一遍WHERE过滤的是原始行HAVING过滤的是分组后的行。一个常见误区是“只要带了聚合函数条件就一定是HAVING”其实如果条件不依赖聚合结果哪怕查询里写了GROUP BY它依然是WHERE。比如“只统计已支付状态的分组订单数”SELECT status, COUNT(*) AS cnt FROM order WHERE pay_status 1 GROUP BY status;这个pay_status 1就是WHERE不是HAVING。判断标准只有一个过滤动作发生在分组前还是分组后。4.3 实用场景用SQL完成统计报表把聚合、分组、HAVING串起来你已经能解决不少真实业务问题了。我举一个实际需求统计7月份每个用户的订单总金额并且只要总金额超过5000元的用户。晚一秒看答案先自己想一下SQL。这个需求拆开是三段逻辑限定时间范围WHERE按用户分组GROUP BY过滤总金额HAVING。SQL如下SELECT user_id, SUM(amount) AS total_amount FROM order WHERE create_time 2024-07-01 AND create_time 2024-08-01 GROUP BY user_id HAVING total_amount 5000 ORDER BY total_amount DESC;这就是一个非常典型的分析型查询没有复杂的嵌套子查询但每个子句各司其职。你在实际工作中写这类统计核心思路就是“先范围缩小、再分组聚合、再条件过滤”把需求翻译成SQL子句的过程越熟练写出来的查询越干净、性能越靠谱。5. JOIN与子查询处理多表数据的核心打法5.1 INNER JOIN与LEFT JOIN的实战场合真实业务里数据很少老老实实待在一张表里。用户表、订单表、商品表天生就有关联关系而JOIN就是把多张表按关联条件拼合起来的桥梁。INNER JOIN内连接只返回两边都能匹配上的记录SELECT u.name, o.order_no, o.amount FROM user u INNER JOIN order o ON u.id o.user_id;这表示“只有下过单的用户才会出现在结果里”没下过单的用户不会出现。LEFT JOIN左连接则保留左表的全部记录右表没匹配上的用NULL填充SELECT u.name, o.order_no, o.amount FROM user u LEFT JOIN order o ON u.id o.user_id;这表示“所有用户都会出现没下过单的订单字段显示为NULL”。两类连接怎么选核心判断依据是你到底需不需要保留左表里没有匹配记录的那些行。实际场景里LEFT JOIN最容易出的问题是“左表原本一条记录JOIN右表却膨胀成多行”。比如用户表一条记录关联了订单表五条记录LEFT JOIN之后用户信息就被复制成五行。这时候如果你在查询里加COUNT(*)统计出来的数量就不是用户数而是“用户和订单的匹配对数”。所以写JOIN前一定要先确认关联字段在右表有没有重复必要时先用DISTINCT或者子查询去重把“一对多”关系压回“一对一”。5.2 自连接与多表连接的思路自连接是JOIN里比较烧脑的一种。它是“同一张表和自己连接”常用于处理表中的上下级关系。举个例子员工表里有id和manager_id想查出每个员工及其领导的姓名SELECT emp.name AS employee_name, mgr.name AS manager_name FROM employee emp LEFT JOIN employee mgr ON emp.manager_id mgr.id;这里的关键思路是给同一张表起两个不同的别名把它想象成“两张结构相同的表”。自连接理论上是查询树形层级数据的入门招式但是要注意一个真正的组织架构可能是多层的比如三级、四级甚至更深。这种多层级场景用单个自连接只能处理一层要查多层就得嵌套多个别名SQL会变得非常难维护。实际开发中如果层级确实很深我更建议在应用层递归组装而不是强行用一条SQL把整棵树查出来。5.3 子查询的三种形态与实际应用子查询是嵌套在另一个查询里的查询。按返回结果形态来分主要有标量子查询、表子查询和关联子查询。标量子查询返回单一值常放在SELECT或WHERE里SELECT name, (SELECT MAX(amount) FROM order WHERE user_id u.id) AS max_amount FROM user u;这个EXISTS和表子查询常用于IN判断-- 查下单超过3次的用户 SELECT name FROM user u WHERE (SELECT COUNT(*) FROM order o WHERE o.user_id u.id) 3;关联子查询的特点是子查询里的表引用外层查询的列它的执行方式是“外层每一行都要执行一次子查询”。这种查询逻辑表达能力很强但当外层表数据量大时性能往往不理想。优化方向一般是用JOIN加GROUP BY或EXISTS改写。关于IN和EXISTS的选择MySQL优化器现在基本都能自动优化但在两个场景下我建议你亲自测试大表驱动小表时EXISTS和小表驱动通常更稳子查询结果集特别大时IN可能导致内部临时表膨胀。实操中最靠谱的做法是用EXPLAIN看执行计划而不是背各种“口诀”。6. 日常开发中的性能排查与避坑清单6.1 EXPLAIN看懂查询执行的“体检报告”SQL写得好不好不能靠感觉得靠执行计划来判断。MySQL里用EXPLAIN关键字加在查询语句前面就能看这条SQL的执行细节EXPLAIN SELECT * FROM user WHERE name 张三;重点关注几个字段type、key、rows、Extra。type是访问类型从好到差大致是systemconsteq_refrefrangeindexALL。看到ALL就意味着全表扫描数据量大时这就是性能瓶颈的代名词。key表示实际用了哪个索引rows是预估扫描行数Extra里如果出现Using filesort、Using temporary说明排序或分组使用了临时文件和临时表这通常会对性能有较大影响要重点优化。我记得有一次定位一个慢查询前端页面加载报表要十几秒。EXPLAIN一看主要问题出在ORDER BY create_time DESC触发Using filesort而create_time列有个不大合适的联合索引导致排序没走索引。后来调整索引字段顺序让查询既命中WHERE条件又能直接用索引完成排序查询直接从秒级降到毫秒级。这类优化没有EXPLAIN是没法对症下药的。6.2 DML性能陷阱大批量操作与锁冲突DML的性能问题归纳起来主要是两类一类是单条语句太重另一类是事务持有锁时间太长。单条语句太重典型的就是UPDATE或DELETE的范围太大、条件列无索引。前面提过一次UPDATE全表可能导致响应超时但如果只更新50万行同样也不轻松。我处理过一个线上问题某定时任务要把7天前的订单状态批量改成“已归档”一次性执行UPDATE order SET archive_status 1 WHERE create_time DATE_SUB(NOW(), INTERVAL 7 DAY);这条语句执行期间订单表的许多行都被锁住后续业务写入全部排队线上直接报警。后来改成按主键分段循环每次只更新5000行问题就消失了。批量任务里引入“分批 间隔”的节奏是对数据库最大的温柔。锁冲突的问题则常见于并发读写同一行数据。两个事务同时修改一条记录后到的一方会等待先到的一方释放行锁没有索引时行锁可能升级为全表锁冲突范围瞬间扩大。所以给 DML 的条件列建索引不光是性能问题也是锁粒度问题。6.3 实战速查常见报错信息与人话解读我把日常开发中高频出现的MySQL报错信息整理成一个速查表方便你直接对照报错信息含义解决方向You have an error in your SQL syntaxSQL语法错误检查关键字拼写、括号是否成对、逗号是否漏掉Unknown column xx in where clause列名不存在检查表结构里是否有该列注意大小写和后缀空格Column xx in field list is ambiguous列名在多个表里都存在含义不明确给列名前加上表名或表别名如u.nameExpression #1 of SELECT list is not in GROUP BY clauseSELECT的非聚合列没出现在GROUP BY中按ONLY_FULL_GROUP_BY要求补全分组列或改用聚合函数Lock wait timeout exceeded锁等待超时检查是否有大事务没提交、条件列是否有索引、是否有死锁Data too long for column xx插入或更新的数据超出字段长度检查字段定义长度或者用SUBSTRING截断数据Duplicate entry xx for key xxx唯一键冲突确认业务上是否允许重复用INSERT IGNORE或ON DUPLICATE KEY UPDATE处理这里再分享一个令人头大的场景Lock wait timeout exceeded出现时很多人第一反应是重启数据库服务这其实是错误做法。重启只能清掉持锁的进程但治标不治本。正确的定位步骤是先查当前有哪些事务在跑SELECT * FROM information_schema.innodb_trx\G找到长时间处于RUNNING状态且trx_query非空的事务再判断是哪个业务SQL持锁不释放最后让对应应用停掉或者KILL掉那个连接。整个过程有点像侦探破案但查一次之后你对事务的理解会直接上一个台阶。7. 写在最后把“会写”变成“写好”回到开头的判断SQL写得好不好确实决定你下班早不早。DML和DQL的核心语法说来说去就那么些关键字但真正拉开差距的是你对执行顺序、索引机制、事务边界和业务语义的理解深度。我最早写SQL的时候也是一个语法查半天、一个分页慢查询折腾一下午的菜鸟后来养成了一个习惯凡是遇到的慢SQL或者语义怪异的SQL都顺手EXPLAIN一下把执行计划和自己的预期对比时间久了看到一条SQL脑子里就能大致浮现出它是怎么跑的。这篇笔记不可能覆盖MySQL的全部知识但把这套DML和DQL的骨架搭扎实再往上补索引优化、事务隔离、窗口函数这些内容时你会觉得非常顺。如果你正在练SQL建议别光看找一套测试数据把文里的每个例子自己敲一遍遇到报错也别急着跳过报错信息里藏着很多学习线索。写SQL是熟练工种多踩几个坑多优化几条慢查询你自然就能从“会写”变成“写好”。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号