ARTICLE DETAIL

资讯详情

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

MySQL增删查改实战指南:从基础语法到性能与安全

MySQL增删查改实战指南:从基础语法到性能与安全 直接说结论MySQL的增删查改是所有数据库操作的基石不管以后你做业务开发、数据分析还是DBA运维最终都逃不过这四类语句。这篇东西不是教科书式的语法清单而是我实际带新人和排查线上问题时反复用到的思路和写法从环境准备开始到增删查改的细节、性能陷阱、安全底线最后附上常见的报错排查尽量让你看完就能上手少走弯路。适合谁看刚接触MySQL的初学者、准备面试的应届生、以及写了好几年SQL但一直在“能用就行”状态的开发者。如果你已经能把SQL写得很溜也可以重点看第4章和第5章那部分是实际业务里真正容易出问题的地方。1. 动手前的准备先把MySQL跑起来1.1 不同环境下的安装方式很多人第一步就卡在安装上。MySQL在不同操作系统下的安装方式差别很大我推荐按场景选择没必要硬学所有方法。Windows最简单下载安装包一路点Next就行。注意选“Server only”还是“Full”日常开发选Server only足够省得装一堆用不上的组件。安装时有一个步骤是设置root密码和选择认证方式这里要选Use Legacy Authentication即 mysql_native_password否则用老版本客户端连接时会报认证插件不兼容的错误。这个坑我见过太多次了。Linux建议优先用系统自带的包管理器# Ubuntu / Debian sudo apt update sudo apt install mysql-server -y # CentOS / RHEL 系列 sudo yum install mysql-server -y离线环境或者内网部署就用rpm包手动安装。需要注意rpm安装有依赖顺序一般按 common → libs → client → server 的顺序装缺依赖时用yum localinstall或rpm -ivh --nodeps处理非必要别加 --nodeps可能留下隐患。另外装了MySQL 8.0之后Linux上通常还需要手动启动服务并设置开机自启systemctl start mysqld systemctl enable mysqldDocker方式适合想快速验证环境的场景一条命令搞定docker run -d --name mysql8 -e MYSQL_ROOT_PASSWORD你的密码 -p 3306:3306 mysql:8.0注意Docker跑MySQL一定要挂载数据卷否则容器一删数据全没了docker run -d --name mysql8 \ -v /data/mysql:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD你的密码 \ -p 3306:3306 mysql:8.01.2 登录与图形化管理工具装好之后验证一下mysql -u root -p能进到这个mysql提示符就说明服务正常。远程连接时如果通信量比较大批处理脚本或数据传输场景下用--compress参数可以省不少带宽比如 mysqldump 或 mysql 客户端配合-C选项。日常开发我建议装一个图形化工具Navicat占多数DBeaver是开源免费的选择后者的离线驱动包可以在官网下载后手动导入对隔离网环境比较友好。1.3 建库建表第一张业务表SQL语句的“增删查改”其实是针对数据行但在那之前得先有库和表。库相当于文件夹表相当于表格文件。建库CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这里我强烈建议统一用utf8mb4不要再用utf8。原因很简单utf8在MySQL里最多存3字节像emoji这种4字节字符存不进去会出现Incorrect string value的报错。这个坑几乎每个初学者都会踩一次干脆从一开始就养成习惯。切换到当前库USE shop;建一张用户表CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 用户名, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, age TINYINT UNSIGNED DEFAULT 0 COMMENT 年龄, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逐个解释关键点AUTO_INCREMENT自增主键每次插入自动加1不需要手动指定。主键字段我用INT UNSIGNED无符号最大能到42亿对绝大多数业务都够用如果预期数据量巨大可以换成BIGINT。NOT NULL和DEFAULT NULL字段是否允许为空尽量在设计阶段想清楚。age设默认值为0不是NULL这里对应了热搜词“mysql设置默认值为0”的场景统计时可以用IFNULL或COALESCE兜底避免空值参与运算导致结果异常。UNIQUE KEY唯一索引保证username不重复同时查询时走索引更快。ENGINEInnoDBMySQL 5.5之后的默认引擎支持事务、行级锁、崩溃恢复。除非你有特殊理由比如全文索引场景用MyISAM否则别换。热搜词里“mysql锁的分类”“mysql事务处理”都和这个引擎直接相关。表建好之后用DESC users;看一下结构再往后就能正式开始增删查改了。2. INSERT把数据写进去2.1 三种插入写法插入数据最基础的就是INSERT INTO ... VALUES。三种常见写法-- 方式一按表结构顺序插入最偷懒但最脆弱 INSERT INTO users VALUES (1, 张三, zhangsanexample.com, 25, NOW()); -- 方式二显式指定字段强烈推荐 INSERT INTO users (username, email, age) VALUES (李四, lisiexample.com, 30); -- 方式三一次插入多条注意字段要一一对应 INSERT INTO users (username, email, age) VALUES (王五, wangwuexample.com, 28), (赵六, zhaoliuexample.com, 32), (孙七, NULL, 22);第二种写法最大的好处是表结构变化时不容易错位。你想想如果哪天有人在表中间加了一列第一种写法会全部错位数据直接写乱。第三种写法一次插入多条效率远高于循环执行单条INSERT每减少一次网络往返写入速度就上一个台阶。2.2 批量插入的性能逻辑批量插入不是只能省几次键入对性能影响非常大。MySQL写入的时候有事务日志redo log、binlog、索引维护这些开销每执行一条INSERT都要经历一次完整的提交流程。把100条数据放在一个INSERT语句里事务提交只有一次而循环执行100次光日志同步就多出几十倍的操作。实际写代码时用Python的executemany或Java的addBatch()就是批量写入底层原理和SQL多值INSERT一致。我实测过批量插入5000行比逐行插入快至少一个数量级。但注意一条INSERT也别塞太多行建议每批500~1000条超过这个量容易导致单个事务过大锁资源占用高反而拖慢性能。2.3 插入时容易踩的坑插入这块最常见的报错我列一下Duplicate entry xxx for key uk_username唯一键冲突。业务代码里要先查再插或者直接用INSERT IGNORE、ON DUPLICATE KEY UPDATE。热搜词“mysql常用sql语句”里有个高赞答案就是这个的写法实际确实高频-- 有则更新无则插入 INSERT INTO users (username, email) VALUES (张三, newexample.com) ON DUPLICATE KEY UPDATE email VALUES(email);Incorrect string value: \xF0\x9F\x98\x80...这就是前面说的字符集问题。表、库、连接三者都要确保是utf8mb4。Data too long for column username字段长度不够VARCHAR(50)存不下输入值。这种往往是业务输入没做校验建议应用层先把长度卡死。Field username doesnt have a default value插入时漏了NOT NULL且没有默认值的字段。2.4 插入和默认值那些事MySQL 8.0里支持在字段定义时直接设默认值也可以用表达式。比如前面表里的created_at DATETIME DEFAULT CURRENT_TIMESTAMP就是在插入时不指定该字段系统自动填当前时间。热搜词里有“mysql设置默认值为0”做法就是age TINYINT UNSIGNED DEFAULT 0。区分一下DEFAULT 0字段默认值是数字0查询出来是0。DEFAULT NULL字段默认是NULL查询出来是NULL不是空字符串。NULL和0在业务含义上完全不同。0是有效值NULL是“没有值”。统计时COUNT(age)不会统计NULL但会统计0这点一定要注意。设计表时想清楚“默认值到底代表什么业务含义”能避免很多后续的脏数据我在实际项目里见过不少因为“默认设0还是NULL”没想通导致报表数字对不上的情况。3. SELECT用得最多也最考验功底3.1 基础查询与WHERE过滤查询是四类操作里语法最灵活、最能体现水平的。最基础的SELECT * FROM users; -- 全表所有行、所有列 SELECT username, email FROM users; -- 只查指定列 SELECT * FROM users WHERE age 25; -- 条件过滤 SELECT * FROM users WHERE age BETWEEN 20 AND 30; SELECT * FROM users WHERE email IS NULL; -- 注意不是 email NULL统一说明几个新手高频错误判断空值必须用IS NULL或IS NOT NULL用 NULL永远查不到结果因为NULL参与等值比较的结果是“未知”不是真也不是假。字符串匹配用LIKE但%和_两个通配符要小心。LIKE 张%匹配以“张”开头LIKE %张%匹配中间含“张”。前缀通配符能走索引%开头会让索引失效数据量大时非常慢。多个条件用AND、OR如果条件太多可以用INSELECT * FROM users WHERE age IN (25, 28, 32);3.2 排序ORDER BY与分页LIMIT数据一多顺序就乱了。按年龄排序SELECT username, age FROM users ORDER BY age DESC; -- 降序 SELECT username, age FROM users ORDER BY age ASC; -- 升序默认就是ASC多字段排序时关键词是排序优先级先按第一个字段排相同再按第二个字段排。比如“先按年龄降序相同年龄按id升序”SELECT * FROM users ORDER BY age DESC, id ASC;分页是Web系统里最常见的需求语法很简单SELECT * FROM users ORDER BY id LIMIT 0, 20; -- 第1页每页20条 SELECT * FROM users ORDER BY id LIMIT 20, 20; -- 第2页LIMIT offset, count第一个数字是偏移量第二个是条数。这里有个经典性能陷阱偏移量很大的时候比如LIMIT 1000000, 20MySQL还是会先把前1000020条全部扫出来再丢掉前100万条效率极低。大数据量翻页建议用“上一页最后一条记录的id”代替偏移量-- 第N页假设上一页最后一条id是999 SELECT * FROM users WHERE id 999 ORDER BY id LIMIT 20;这个“基于游标的分页”实际项目里特别实用我在用户量过千万的表上对比过差距是两个数量级。3.3 聚合统计与分组统计是SQL的另一大核心能力。常用聚合函数SELECT COUNT(*) FROM users; -- 总行数 SELECT COUNT(age) FROM users; -- 非NULL的age数量 SELECT MAX(age), MIN(age), AVG(age) FROM users; SELECT SUM(age) FROM users;分组统计配合GROUP BY使用。比如按年龄段统计人数SELECT CASE WHEN age 20 THEN 20以下 WHEN age BETWEEN 20 AND 30 THEN 20-30 ELSE 30以上 END AS age_group, COUNT(*) AS cnt FROM users GROUP BY age_group;注意GROUP BY后面能直接用SELECT里起的别名这个在MySQL里是允许的Oracle和SQL Server则不行别写顺手了换数据库时踩坑。GROUP BY之后如果要过滤分组结果不能用WHERE要用HAVING这个区别每年面试都有人错SELECT age, COUNT(*) AS cnt FROM users GROUP BY age HAVING cnt 1;WHERE在分组前过滤原始行HAVING在分组后过滤分组结果。逻辑先后关系不是语法偏好问题。3.4 多表连接JOIN实际业务几乎没有只查一张表的。订单表和用户表商品表和分类表都需要连表查。JOIN有三种-- INNER JOIN两张表都匹配上的行 SELECT u.username, o.order_no FROM users u INNER JOIN orders o ON u.id o.user_id; -- LEFT JOIN左表全部行右表没有就补NULL SELECT u.username, o.order_no FROM users u LEFT JOIN orders o ON u.id o.user_id; -- RIGHT JOIN右表全部行左表没有就补NULL用的相对少实际用得最多的是LEFT JOIN主要场景是“以某表为主查出它的所有记录同时关联另一张表的附加信息”。还有一个高频面试问题ON和WHERE的区别。在LEFT JOIN中ON决定右表如何匹配WHERE对连接结果再过滤。如果过滤条件放在WHERE里可能导致左表某些行被剔掉效果变成和INNER JOIN一样了。这个细节影响业务正确性不是语法问题。4. UPDATE与DELETE改与删的安全底线4.1 UPDATE语法与执行流程更新单条数据UPDATE users SET age 26 WHERE id 1;同时更新多个字段UPDATE users SET age 26, email newexample.com WHERE id 1;这里必须说一句没有WHERE条件的UPDATE会更新全表所有行。这不是恐吓是真的会让你瞬间把测试环境甚至生产环境的数据全改掉。我见过不止一次有人在执行脚本时漏了WHERE几秒钟内几百万人收到错误的会员等级变更最后只能从备份恢复。更新语句执行时InnoDB引擎会对命中的行加排他锁X锁事务提交或回滚后才释放。这就是热搜词里“mysql锁表”的来源之一。如果同时有多个事务更新同一行后到的事务会阻塞等待直到前面的提交或回滚。线上操作大表更新时批量操作最好分批提交防止一个超长事务锁住大量行拖垮整个库。4.2 DELETE语法与TRUNCATE的区别删除数据和更新一样核心风险也是漏写WHEREDELETE FROM users WHERE id 10; -- 删除指定行 DELETE FROM users WHERE age 18; -- 按条件删除多行 DELETE FROM users; -- 清空全表危险清空全表还有个更快的方案TRUNCATE TABLE users。两者区别很重要对比项DELETETRUNCATE逐行删除触发行锁是否保留自增ID序号保留重置为1可加WHERE条件可以不可以可以通过事务回滚可以不可以隐式提交速度慢快很多实际业务里如果只是想清空一张临时表并让自增ID重新从1开始用TRUNCATE。如果需要按条件精准删除或者删除后可能需要回滚只能用DELETE。还有一类场景是“软删除”——不是真删而是加一个deleted字段标记查询时默认带上WHERE deleted 0。这在电商和SaaS系统里非常常见原因很简单用户要求“恢复数据”如果物理删了恢复只能靠倒备份成本极高而软删除只需要UPDATE ... SET deleted 0。我推荐业务表都预留这个字段除非有极强的数据归档需求。4.3 事务与误操作恢复DELETE和UPDATE都支持事务回滚前提是你把操作放在事务里START TRANSACTION; DELETE FROM users WHERE id 10; -- 如果发现删错了 ROLLBACK; -- 如果确认没问题 COMMIT;实际项目中改一堆数据前后应该开启显式事务而不是让每条语句自动提交——默认情况下MySQL是每条UPDATE和DELETE自动COMMIT的一旦执行就永久生效。这也是为什么我们强烈建议线上改数据之前先SELECT查一遍 WHERE 命中多少行SELECT COUNT(*) FROM users WHERE id 10; -- 先确认就1条然后再开事务执行修改。这算是我个人的铁律宁可多敲两行也绝不直接改。5. 常见问题与排查技巧实录5.1 服务起不来、登录不了热搜词里有个很典型的错误“net start mysql mysql 服务无法启动”。Windows下常见原因是服务名不对或数据目录权限异常先用命令查sc query mysql mysql --version如果提示没有配置文件可以手动指定mysqld --defaults-fileC:\ProgramData\MySQL\MySQL Server 8.0\my.iniLinux下常见是/var/lib/mysql权限问题chown -R mysql:mysql /var/lib/mysql就能解决。还有防火墙放行3306端口的问题远程连不上十有八九是这个。另一类高频是“mysql ssl连接错误”。MySQL 8.0默认开启SSL客户端连接时如果协议不匹配会报类似SSL connection error。解决方法是在连接串加参数jdbc:mysql://host:3306/db?useSSLfalseallowPublicKeyRetrievaltrue如果是Navicat连接高级选项里把“使用SSL”关掉试试。顺便说一句allowPublicKeyRetrievaltrue如果不开新版驱动用caching_sha2_password认证时可能会报Public Key Retrieval is not allowed这也是MySQL 8.0默认认证方式带来的连锁变化。5.2 SQL执行报错速查我把平时遇到最多的一批报错整理成表格遇到时可以快速对照报错信息原因解决方案Unknown column xxx in field list列名拼写错误或确实不存在DESC 表名看真实字段Table xxx doesnt exist表不存在或没选对库确认USE的库名You have an error in your SQL syntaxSQL语法错误注意引号逗号看报错位置附近的字符Lock wait timeout exceeded行锁等待超时查information_schema.innodb_trx找阻塞事务Data too long for column超出字段长度调整VARCHAR长度或业务侧截断Incorrect string value字符集问题全面改utf8mb4Duplicate entry主键或唯一键冲突用INSERT IGNORE或ON DUPLICATE KEY UPDATE5.3 误操作之后怎么办先说最慌的情况没有事务包裹的DELETE或UPDATE误操作已经执行了。这时候稳住按下面顺序处理立刻停止所有相关的写入操作防止数据被覆盖得更彻底。如果开启了binlog线上一般都会开用mysqlbinlog工具解析binlog找到误操作前的数据生成反向SQL恢复。如果没有binlog只能用最近的物理备份或逻辑备份恢复恢复到误操作前的状态再导出受影响行的数据。日常最好每天做全量备份重要表做实时或准实时备份。备份命令先记住这个mysqldump -u root -p --single-transaction --routines --triggers shop shop_backup.sql--single-transaction在InnoDB下可以保证导出期间数据一致性而且不加锁不影响在线业务读取。恢复就是反向的事mysql -u root -p shop shop_backup.sql还有热搜词里提到的“mysql既然叫增删查改”实际运维最该多做的一件事就是备份。备份不是给DBA用的是给所有人兜底的。我自己有个习惯任何一次“高危操作”批量UPDATE、DELETE、ALTER TABLE执行前一定先备份目标数据用CREATE TABLE xxx_bak_20250101 AS SELECT * FROM xxx;这种快速方式也行至少能多一条活路。5.4 字符集乱码排查字符集问题是中文环境下的老朋友。乱码的根本原因是“写入时用的编码”和“读取时用的编码”不一致。排查时一口气查三层SHOW VARIABLES LIKE character_set_server; -- 服务端 SHOW VARIABLES LIKE character_set_database; -- 数据库 SHOW VARIABLES LIKE character_set_connection; -- 连接层查出来的结果如果不是utf8mb4把它们统一改成utf8mb4。MySQL 8.0默认已经是utf8mb4了5.7及以下版本需要手动设置。连接串里也可以指定jdbc:mysql://host:3306/db?useUnicodetruecharacterEncodingutf8一旦建库时用错了字符集后面想改就麻烦因为已有数据改编码容易产生乱码或数据损坏。最好的办法是建库时就固定用utf8mb4一劳永逸。6. 基础之外的进阶方向增删查改只是入口。把四类语句写熟之后下面这几个方向是实际工作里一定会碰到的提前留个概念索引优化为什么某条查询特别慢用EXPLAIN看执行计划。EXPLAIN SELECT * FROM users WHERE age 30;如果看到typeALL或者rows很大说明走了全表扫描。索引不是越多越好每个索引都有写入成本和存储成本。热搜词里“mysql创建索引”“mysql性能调优”就是这块。存储过程与函数把复杂的业务逻辑封装在数据库端适合批量报表、数据迁移等场景。但现代应用架构下不建议把核心业务逻辑写在存储过程里代码难调试、难版本管理。锁机制行锁、表锁、间隙锁以及死锁的检测与规避。面试常考“MySQL锁的分类”实际线上排查死锁也是硬功夫。连接池后端应用不会每次请求都新建数据库连接成本太高。Druid、HikariCP、C3P0这些连接池帮你复用连接配置核心参数是初始大小、最大大小、空闲超时。热搜词里“mysql的数据库连接池”指的就是这个。数据同步与异构比如热搜词里“使用flink实现mysql同步到clickhouse”和“mysql表结构自动转tdengine超级表子表”这类属于数据集成场景原理是先通过binlog监听MySQL变更CDC再写入目标系统。听懂原理再看工具就不慌了。这些方向每个都能单独写几千字。我的建议是先把基础操作的熟练度提上来做到接需求能条件反射地写出正确的SQL再往这些方向深入。数据库这块基础操作熟练度上来了后面学什么都快。最后分享一个小技巧平时练习时故意把SELECT *改成只写需要的字段名一是减少网络传输二是明确数据范围。这个习惯看着不起眼但能帮你避免很多兼容性问题和隐式依赖。增删查改看起来简单真正写得好的人写出来的每条语句都清楚自己在干什么为什么这么写。
返回列表