ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL增删查改实战笔记:CRUD核心语法与索引事务避坑指南

MySQL增删查改实战笔记:CRUD核心语法与索引事务避坑指南 说真的写了这么多年代码被人问得最多的还是MySQL“增删查改”。很多新手觉得这东西不是“有手就行”吗可实际一上手要么where条件写错导致全表更新要么insert语句字段对不上报错要么一个不带索引的查询把线上库拖垮。这篇文章我就把MySQL最核心的增删查改CRUD从头到尾捋一遍结合我这些年实操中踩过的坑、优化过的慢查询、以及面试里常考的点写成一份能直接照着用的实战笔记。内容涉及建表规范、字段类型选择、四种基本操作的完整SQL写法、排序分页聚合进阶、事务与锁的避坑指南还有常见报错的排查速查表。不管是刚接触MySQL的学生还是需要快速上手数据库开发的转行者或者想系统性查漏补缺的初中级开发这篇文章都值得你花十五分钟读完我保证没有一个字是废话。先把环境准备好我们直接进入正题。1. 环境准备与基础认知工欲善其事必先利其器1.1 安装与部署本地开发环境怎么选MySQL的安装方式很多但不同场景有不同选择。我自己最初是在Windows上直接用安装包搞定的现在装的是MySQL 8.0一路Next就能完成唯一需要注意的是选Server Only还是Developer Default。如果你不想装一大堆用不上的组件选Server Only就够了Workbench等工具后面需要再单独装也不迟。对于有Docker环境的同学我强烈推荐用容器跑MySQL尤其是需要多个版本测试或者随时可以丢弃重来的场景。一条命令就能搞定docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEtestdb \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci这里有几个细节值得说清楚。-e MYSQL_DATABASEtestdb会在容器启动时自动创建一个名为testdb的库省去手动建库的麻烦--character-set-server和--collation-server参数是必须指定的否则MySQL 8.0默认的utf8mb4字符集还算好但排序规则如果不指定你后续处理中文排序或索引优化时会遇到一些莫名其妙的问题。更关键的是utf8mb4字符集一定要会选它在MySQL里才是真正的“完整UTF-8”支持四字节emoji等特殊字符而早期理解的utf8在MySQL中实际上是utf8mb3存emoji直接报错。我这句是真心经验当初做移动端业务时因为字符集选错用户昵称带个表情接口直接500排查了两个小时才定位到编码问题。MySQL 8.0和5.7的差异也比较大8.0默认字符集已经是utf8mb4且取消了查询缓存自带的认证插件是caching_sha2_password。如果使用的是旧版JDBC驱动连接8.0会报“Client does not support authentication protocol requested by server”。网上关于这个报错的解决方案很多但最终根子还是在驱动版本上Java项目需要把mysql-connector-java升级到8.0.x这个问题就自然消失了。1.2 客户端工具盘点不要再死磕命令行命令行很酷但日常开发调试时一个好用的图形化工具能让效率翻倍。我自己长期用的是MySQL Workbench和dbx数据库工具。Workbench是官方出品的功能全面支持ER图、SQL编辑、性能监控但这些功能对新手来说有点重了dbx工具则轻量得多双击库表直接查看数据点击单元格直接编辑适合快速排查问题。不过要注意凡是通过图形化工具直接修改数据务必先看工具左下角有没有自动包裹事务否则手滑删了一行数据又没有begin/rollback神仙都救不了你。除了图形化工具我还建议你趁早学会看EXPLAIN执行计划。这是MySQL数据库性能分析最重要的工具没有之一。一条慢查询进来先用EXPLAIN SELECT ...看看是不是用了索引、扫描了多少行再进行优化。我在第四节会专门展开讲。2. 增删查改的核心语法拆解CRUD是个技术活2.1 INSERT插入单行、多行、批量来源插入数据是最基础的操作但要注意的细节一点也不少。先看最简单的单行插入INSERT INTO users (name, age, email) VALUES (张三, 28, zhangsanexample.com);这里有几个容易踩的坑。字段名和值列表必须一一对应顺序不能乱自增主键id字段不需要出现在字段列表里可以为NULL的字段可以省略但NOT NULL且无默认值的字段绝对不能省。如果你非要尝试省略NOT NULL字段MySQL会直接给你一个says “Field xxx doesnt have a default value”这条报错后续我们还会在速查表里再提。多行插入的写法可以大大减少SQL语句与数据库的交互次数INSERT INTO users (name, age, email) VALUES (李四, 25, lisiexample.com), (王五, 30, wangwuexample.com), (赵六, 22, zhaoliuexample.com);一次插入几百条数据时多行VALUES的效率提升非常明显尤其适合初始化数据或批量刷数据脚本。我实际测试过同样是插入一万条用多行VALUES比循环单条insert快出好几个数量级这种差异在数据量上来后完全是天壤之别。还有一种实战中特别常用的插入方式就是把查询结果直接插入另一张表INSERT INTO user_backup (name, age, email) SELECT name, age, email FROM users WHERE age 30;这个语法在做数据归档、临时表加工、表结构拆分时非常省事。你不需要先把数据查出来再在代码里遍历再一条条插一条SQL就搞定了。2.2 DELETE删除与UPDATE更新先想清楚条件再动手删除和更新我都放在一起讲因为它们的共同点是都必须把WHERE条件放在第一位去思考。我见过太多事故比如本来想更新某一条数据结果忘了写WHERE或者条件写错导致全表数据被覆盖。MySQL里UPDATE不带WHERE会更新所有行DELETE不带WHERE会清空整张表。这虽然是常识但每个DBA的日常里都会有类似的救人故事。先看UPDATEUPDATE users SET age 29, email zhangsan_newexample.com WHERE id 1;SET子句可以同时更新多个字段用逗号分隔。WHERE条件决定了这次更新影响哪些行强烈建议先写WHERE再回头写SET或者在客户端里选中某一行再生成UPDATE语句。在Workbench、dbx这类工具里你可以用WHERE限定主键后再执行别嫌麻烦养成习惯能保命。再看DELETEDELETE FROM users WHERE id 1;如果只是想清空表数据但保留表结构用TRUNCATE TABLE users;会比DELETE FROM users;快得多因为它不逐行删除而是直接重建表。但要注意TRUNCATE不能有WHERE条件也不能触发删除类的触发器而且会重置自增IDDELETE则不会重置自增ID。所以如果需要保留自增序号继续递增不能用TRUNCATE。另外一个重要区别是DELETE支持事务回滚而在某些场景下TRUNCATE不一定能完全按你期望的方式回滚生产环境里用TRUNCATE要格外谨慎。2.3 SELECT查询从简单查询到条件组合SELECT是CRUD里最常写也最考验功底的语句。最简单的查询SELECT * FROM users;这个语句能跑通但尽量不要在业务代码里用。多写一行字段名不是为了显得专业而是为了接口返回稳定、减少不必要的数据传输、也方便以后加字段时避免意外泄露敏感信息。正确做法是明确列出需要的字段SELECT id, name, age FROM users;带条件的查询是日常工作主力SELECT id, name, age FROM users WHERE age 25 AND name 张三;WHERE子句支持各种运算符和组合常用的有 、BETWEEN AND、IN、LIKE、IS NULL、AND、OR。这里我特别想提醒几个容易出问题的点。LIKE模糊查询的规范用法是WHERE name LIKE 张%表示以“张”开头的名字。但如果你写LIKE %张%MySQL在大多数情况下无法使用普通索引BTree索引的前缀匹配特性决定了它必须知道开头才能走索引全表扫描在所难免。数据量小的时候无所谓数据量到了百万级这种查询就是慢查询的活跃分子。IS NULL也要注意判断字段为空必须用IS NULL而不是 NULL。在MySQL里NULL NULL的结果是NULL即“未知”所以WHERE条件永远不会命中。很多人刚接触数据库时在这个地方绕很久我当初也是被同事指出来才恍然大悟。2.4 SELECT进阶排序、分页、聚合与分组查询还有一个高频需求就是排序。单字段排序很简单SELECT id, name, age FROM users ORDER BY age DESC;ORDER BY后面可以跟多个字段用英文逗号分隔每个字段可以单独指定ASC升序或DESC降序SELECT id, name, age, created_at FROM users ORDER BY age DESC, created_at ASC;这里MySQL的排序规则会受字符集排序规则影响尤其中文排序在某些情况下不是按拼音或笔画来的而是按编码排序。如果业务对中文排序有要求可以在查询时指定ORDER BY name COLLATE utf8mb4_zh_0900_as_cs但说实话这类需求大部分场景都用不到我提这一嘴是让你心里有底真遇到时别抓瞎。分页查询是列表页的标配“LIMIT偏移量, 条数”写法很常见SELECT id, name, age FROM users ORDER BY id LIMIT 0, 20;这表示从第0行开始取20条记录。偏移量越来越大时LIMIT会有性能问题比如LIMIT 1000000, 20会先扫描100万行再扔掉。优化思路通常是通过子查询先拿到目标范围的主键再关联查询具体数据SELECT u.id, u.name, u.age FROM users u INNER JOIN ( SELECT id FROM users ORDER BY id LIMIT 1000000, 20 ) t ON u.id t.id;这种写法虽然复杂一点但在数据量大的场景下性能提升非常明显。聚合查询配合GROUP BY是数据分析的基石。比如统计各个年龄段的用户数SELECT age, COUNT(*) AS cnt FROM users GROUP BY age HAVING cnt 10 ORDER BY cnt DESC;这里要特别注意HAVING和WHERE的区别。WHERE是在分组前过滤的HAVING是在分组后过滤的。如果条件是针对聚合结果的比如找“人数大于10的年龄段”只能写在HAVING里如果条件是针对原始行的比如只需要年龄大于18的用户参与统计用WHERE放前面效率更高。3. 实战案例从建表到业务查询的完整链路3.1 设计一张用户订单表字段类型怎么选光说理论不过瘾我们直接设计一张用户订单表把所有CRUD操作串起来。假设我们正在做一个简单的电商系统需要记录用户下单信息。建表语句如下CREATE TABLE user_order ( id BIGINT UNSIGNED AUTO_INCREMENT COMMENT 主键ID, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, user_id BIGINT UNSIGNED NOT NULL COMMENT 用户ID, product_name VARCHAR(128) NOT NULL COMMENT 商品名称, product_price DECIMAL(10,2) NOT NULL COMMENT 商品单价, quantity INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 购买数量, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 订单状态 0待支付 1已支付 2已发货 3已完成 4已取消, remark VARCHAR(255) DEFAULT NULL COMMENT 备注, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT用户订单表;这个建表语句里有很多细节值得展开讲。id字段用BIGINT UNSIGNED是因为订单量一旦上来INT的最大值远不够用。MySQL的INT是有符号的最大21亿多对很多系统来说确实够用但电商订单这种高频场景还是有溢出风险用BIGINT比较稳妥。order_no字段加了UNIQUE唯一索引这是订单号业务的硬性要求不允许重复。有人可能说通过程序判重不就行了但并发场景下程序判重有竞态条件直接靠数据库的唯一约束兜底才是最安全的方案。后文我会单独讲唯一约束遇到已有重复数据的处理。total_amount字段用DECIMAL而不是FLOAT或DOUBLE这是金额字段的铁律。FLOAT和DOUBLE是浮点数存储时会有精度损失。我打个比方你存0.1取出来可能变成0.100000001490116这种误差在累加时会被放大导致对账不平。DECIMAL是定点数按十进制存储精度可控。至于为什么是(10,2)表示总共10位数字其中2位小数能表达的最大值是99999999.99对绝大多数业务场景够用了。status字段用TINYINT而不是VARCHAR或ENUM是为了节省存储空间并且方便扩展。很多新人在设计状态字段时喜欢直接用字符串比如pending、paid可读性确实好但性能差一些而且一旦枚举值写错了大小写排查起来很麻烦。用TINYINT配合代码注释是目前的主流做法。created_at字段用DATETIME并在设计时让数据库自动填充默认值。MySQL 8.0中DATETIME支持默认值CURRENT_TIMESTAMP非常方便插入时不需要显式传值。updated_at配合ON UPDATE CURRENT_TIMESTAMP会在每次UPDATE时自动更新时间省去了在业务代码里手动维护更新时间的麻烦。最后一点表名和字段名最好用反引号包起来虽然很多时候不包也能运行但万一字段名和MySQL保留字冲突了这个习惯能救你一次。我见过有人给字段取名叫order或group写SQL时报错找半天就是因为没加反引号。3.2 构造测试数据快速插入一条和多条表建好了我们来插入几条测试数据INSERT INTO user_order (order_no, user_id, product_name, product_price, quantity, total_amount, status, remark) VALUES (NO20250101001, 1001, 无线机械键盘, 399.00, 1, 399.00, 1, 前端同事强烈要求), (NO20250101002, 1001, 4K显示器, 2499.00, 1, 2499.00, 2, NULL), (NO20250101003, 1002, USB-C扩展坞, 199.00, 2, 398.00, 0, 办公室备用), (NO20250101004, 1003, 人体工学椅, 1599.00, 1, 1599.00, 3, 腰痛救星);从这几条数据就可以看出一个常见问题的解法订单总金额的生成。有人会问total_amount到底是前端传过来还是后端算的正规做法是在后端根据单价和数量自己计算绝对不信任前端传的任何金额字段。比如INSERT INTO user_order (order_no, user_id, product_name, product_price, quantity, total_amount, status) VALUES (NO20250101005, 1004, 显示器支架, 299.00, 2, 299.00 * 2, 0);在INSERT的VALUES里直接写299.00 * 2让MySQL帮你算总价比在代码里传一个总价要靠谱得多至少前端改了这个字段的值也不会被信任。3.3 典型业务查询把前面的语法串起来有了订单表和数据我们就可以做各种业务查询了。比如要查看用户1001的所有订单按下单时间倒序排SELECT id, order_no, product_name, total_amount, status, created_at FROM user_order WHERE user_id 1001 ORDER BY created_at DESC;再比如统计每个用户的订单总金额和订单数SELECT user_id, COUNT(*) AS order_count, SUM(total_amount) AS total_spent FROM user_order GROUP BY user_id HAVING COUNT(*) 1 ORDER BY total_spent DESC;这个查询很有代表性。GROUP BY user_id把每个用户的订单聚合成一行COUNT统计订单数SUM累加总金额。HAVING和ORDER BY的位置要与字段顺序严格匹配写错顺序并不会报语法错误但会按错误的顺序排序或过滤导致结果与预期不一样。再来看一个典型的“分页筛选排序”组合SELECT id, order_no, user_id, product_name, total_amount, status, created_at FROM user_order WHERE status 1 ORDER BY created_at DESC LIMIT 0, 10;这条语句是管理后台订单列表最常用的形态。它在业务上表示查看第一页的“已支付”订单每页10条。这里status字段有索引created_at排序如果要高效的话理想情况下需要(status, created_at)的联合索引在订单量大的系统里这已经属于索引优化范畴了。4. 工作中最常踩的坑索引、事务与性能隐患4.1 慢查询的源头没走索引在讲其他操作之前我想先把索引说透因为索引直接决定了你的SELECT、UPDATE、DELETE会不会把数据库拖垮。MySQL InnoDB存储引擎使用BTree索引结构这个结构天然支持范围查询和排序。单列索引可以加速等值查询和范围查询但有个经典规则叫“最左前缀原则”。举个例子假设我们在user_order表上建了一个联合索引KEY idx_user_status (user_id, status)那么以下查询都能用到这个索引WHERE user_id 1001 WHERE user_id 1001 AND status 1 WHERE user_id 1001 ORDER BY status但如果查询条件只有WHERE status 1没有user_id作为前缀这个联合索引就用不上因为索引是按照user_id、status的顺序建立的跳过了第一个字段后面的字段就无法定位。这就像查字典时只知道“偏旁”而不知道“部首”没法二分查找只能顺序翻页。判断一条SQL有没有用到索引最直接的办法就是使用执行计划EXPLAIN SELECT * FROM user_order WHERE user_id 1001;看type字段如果显示ref或const说明走了索引如果显示ALL说明是全表扫描。rows字段会显示预估扫描的行数行数越大查询越慢。4.2 事务与锁并发更新时容易出问题先从一个真实场景说起。假设用户下了订单我们分两步执行先扣减库存再更新订单状态。如果这两步之间没有任何保护比如扣完库存后服务崩溃了订单状态没更新数据库里就出现了“已扣库存但订单还是待支付”的脏数据。更麻烦的是如果两个请求同时给同一件商品扣库存都读到剩余数量1然后各自扣减实际剩了-1这个问题就严重了。事务就是用来解决这类问题的。MySQL中InnoDB默认事务隔离级别是REPEATABLE READ可重复读加START TRANSACTION可以开启事务START TRANSACTION; UPDATE product SET stock stock - 1 WHERE id 100; UPDATE user_order SET status 1 WHERE order_no NO20250101001; COMMIT;如果中途出错执行ROLLBACK;就能回滚到事务开始前的状态。需要注意的是事务里执行的UPDATE、DELETE会锁住相关行在未提交前其他事务对这些行的修改会被阻塞。如果两个事务互相等待对方释放锁就会出现死锁MySQL检测到死锁会自动回滚其中一个事务业务代码里要做好重试或报错处理。这就是热词里“数据库死锁”的常见来源。死锁的经典场景是两个任务都先更新表A再更新表B另一个任务先更新表B再更新表A两者相互等锁。解决办法是尽量按相同的顺序访问表和行缩小事务范围及时提交。4.3 唯一约束与重复数据的处理前面建表时我们给order_no加了UNIQUE KEY但很多人会遇到这种情况业务刚开始时没有加唯一约束线上已经积累了重复数据现在想加上去MySQL却报了“Duplicate entry”错误。这时候不能直接加唯一索引得先清理重复数据。清理思路是先找出重复的order_noSELECT order_no, COUNT(*) AS cnt FROM user_order GROUP BY order_no HAVING COUNT(*) 1;然后决定保留哪一条。常用策略是在每条重复记录里保留最小id的那一条删除其余重复项可以用一个临时表来实现DELETE u1 FROM user_order u1 INNER JOIN user_order u2 ON u1.order_no u2.order_no AND u1.id u2.id;这个SQL的含义是如果同一订单号存在更小的id就删掉当前这条更大的id最终每个订单号只保留id最小的一条。执行完确认数据无误后再添加唯一约束ALTER TABLE user_order ADD UNIQUE KEY uk_order_no (order_no);这种操作建议先在测试环境完整演练一遍备份好生产数据后再执行。4.4 常见报错与排查速查表我在实际工作中遇到过很多MySQL报错现整理成一个速查表全是我个人实战中碰过的可以收藏备用报错信息原因分析解决方案Client does not support authentication protocol requested by server客户端驱动版本太旧不支持MySQL 8.0的caching_sha2_password认证插件升级JDBC驱动到8.0.x或者创建用户时指定mysql_native_passwordData too long for column xxx插入或更新的数据长度超过字段限制调整VARCHAR长度或检查是否误用了TEXT类型Field xxx doesnt have a default valueNOT NULL字段未提供值且没有默认值插入时显式提供该字段值或给字段设置默认值Duplicate entry xxx for key uk_xxx插入的数据违反唯一索引约束确认业务逻辑是否需要唯一先查重复再插入或使用INSERT IGNORE / ON DUPLICATE KEY UPDATEDeadlock found when trying to get lock两个事务互相持有对方需要的锁统一事务内的资源访问顺序缩短事务时间加索引减少锁范围You cant specify target table for update in FROM clauseMySQL不允许在UPDATE时直接查询同一张表套一层子查询作为临时表Lock wait timeout exceeded事务长时间占用行锁其他事务等待超时检查是否有未提交的事务用SHOW PROCESSLIST排查Specified key was too long索引字段过长超过限制减小VARCHAR长度或对长文本用前缀索引Multisim访问数据库发生错误第三方工具连接MySQL时网络或权限问题检查MySQL端口是否开放授权用户是否允许远程登录这里重点说一下“You cant specify target table for update in FROM clause”这个报错。举一个我改过的错误示例你想把orders表中所有金额大于平均值的订单标记为特殊状态很多人会直接写UPDATE user_order SET remark 高额订单 WHERE total_amount (SELECT AVG(total_amount) FROM user_order);MySQL会直接拒绝因为不允许在UPDATE时直接SELECT同一张表。解决办法是套一层子查询UPDATE user_order SET remark 高额订单 WHERE total_amount (SELECT avg_amount FROM (SELECT AVG(total_amount) AS avg_amount FROM user_order) AS t);加上一层临时表别名MySQL就允许了。这种细节问题面试时也经常用来考察候选人对MySQL语法的熟悉程度。5. 进阶延伸存储过程与面试高频问题5.1 存储过程把多步CRUD封装起来如果你只是在应用代码里写SQL可能很少用到存储过程。但当你需要做批量数据初始化、定时任务、或者后端不方便处理的多步数据操作时存储过程能把逻辑封装在数据库内部减少网络往返执行效率也更高。我们来看一个简单的存储过程例子它创建一个用户并初始化一个默认订单DELIMITER // CREATE PROCEDURE sp_create_user_with_order( IN p_name VARCHAR(50), IN p_age INT ) BEGIN DECLARE v_user_id BIGINT; INSERT INTO users (name, age) VALUES (p_name, p_age); SET v_user_id LAST_INSERT_ID(); INSERT INTO user_order (order_no, user_id, product_name, product_price, quantity, total_amount, status) VALUES (CONCAT(NO, DATE_FORMAT(NOW(), %Y%m%d%H%i%s), FLOOR(RAND()*1000)), v_user_id, 新人礼包, 0.01, 1, 0.01, 0); END // DELIMITER ;这个存储过程里有几个值得关注的点DELIMITER //和DELIMITER ;的作用是临时修改SQL语句的结束符因为存储过程内部包含多条语句默认的分号结束符会导致只执行第一条语句所以需要用新分隔符告诉MySQL“这是一整个存储过程”。DECLARE v_user_id BIGINT;声明了一个局部变量。LAST_INSERT_ID()函数返回当前会话中最近一次INSERT产生的自增ID注意不要在企业应用里跨会话使用否则会得到别人的ID。CONCAT函数做了字符串拼接生成一个看起来像订单号的字符串企业中要更严谨的订单号生成方案但这里演示够用。调用存储过程很简单CALL sp_create_user_with_order(测试用户, 20);存储过程能把多条SQL打包成一个原子操作配合事务使用要么全部成功要么全部失败非常适合在初始化脚本、数据修复、批量任务中使用。5.2 面试里高频出现的CRUD相关问题我在看简历时几乎每个后端候选人都会写“熟悉MySQL增删查改”。但真到了面试环节能把这四个字讲深的人很少。这里我把常考的问题和考察点列出来你提前准备不吃亏。第一个常见问题DELETE FROM table和TRUNCATE TABLE table有什么区别考察点包括DELETE是DML逐行删除TRUNCATE是DDL直接重建表DELETE可以带WHERE条件TRUNCATE不行DELETE不会重置自增IDTRUNCATE会DELETE在足够权限下可以回滚TRUNCATE在MySQL InnoDB下表现不同DELETE执行慢TRUNCATE执行快。能说出三到四个点以上面试官基本认可。第二个问题CHAR和VARCHAR的区别CHAR是定长字符串最大255字节VARCHAR是可变长字符串最多65535字节实际受行大小限制。CHAR在存储时可能浪费空间但某些场景下访问更快VARCHAR按实际长度存储更节省空间。对于频繁更新且长度稳定的字段如手机号、身份证号用CHAR是合理的对于描述类文本用VARCHAR。第三个问题一条UPDATE语句在MySQL内部是怎么执行的考察点比较深。它需要先经过分析器、优化器在存储引擎中定位到目标行加锁更新行数据写undo日志和redo日志最后提交。这里设计到的是InnoDB事务和日志机制不是单纯CRUD知识但能体现你对自己写的每条SQL背后发生了什么有真正的理解。第四个问题什么是SQL注入它通过拼接SQL字符串实现攻击。防范方法也很直接使用参数化查询PreparedStatement加占位符千万不要把用户输入直接拼进SQL字符串。JDBC里应该这样写String sql SELECT * FROM users WHERE name ? AND age ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, name); ps.setInt(2, age); ResultSet rs ps.executeQuery();有面试官会问“为什么PreparedStatement能防注入”本质原因是占位符参数会被当作数据而不是SQL语句来解析用户输入再包含单引号或分号也不会改变SQL的语法结构。这个问题很经典我希望能帮到你。第五个问题如何优化一条慢查询回答思路是先用EXPLAIN看执行计划判断是否走了索引然后再看是否需要优化SQL写法、增加索引、改写为覆盖索引查询、调整数据库配置或拆分大事务。面试官想听的顺序是“分析在前优化在后”不是上来就加索引。其实围绕CRUD衍生的问题还有不少比如“MySQL中int(5)和int(10)有什么区别”很多人以为括号里的数字控制存储长度实际不是。INT(5)里的5是显示宽度不是存储长度而且配合ZEROFILL才有意义存储范围跟INT本身完全一致。这个问题我在面试中问过很多人能准确答出来的不到三成。写在最后的小建议我最想说的是CRUD看起来简单所以很多人不重视但数据库里大部分线上事故都出在“看似简单”的这几个操作上。我给你的建议是每条SQL写完后先问自己三个问题——WHERE条件写全了吗会影响多少行数据走索引了吗花半秒钟的思考可以省下半夜被电话叫醒去救库的巨大代价。还有一件小事值得养成习惯在开发环境里把所有DML语句包裹在事务中测试顺手验证回滚是否能生效。另外给生产库配置一个类似binlog2sql的开源工具一旦误操作导致数据异常可以从binlog里逆向出原始SQL把丢失的数据找回来。这个技巧我多次用过每次都觉得是真香。最后再分享一个我个人的写SQL习惯在每条SQL的头部注释里写清楚“这条SQL是干什么的、由谁在什么场景下使用的”看着像废话但半年后你回来看一段陌生的SQL就知道这个习惯有多重要。增删查改是这个行业的基石踏踏实实把这四个字吃透后面的路才会越走越稳。
返回列表