资讯动态

MySQL高效查询实战:从增删改查到索引与执行计划优化

发布时间:2026/9/26 17:37:07 来源:尧图企业网站定制
做后端开发这些年MySQL几乎是绕不开的坎。很多人一开始学的时候觉得增删改查就是四个SQL关键字背熟就完事了可真到了线上一遇到几百万行的订单表一个慢查询能把整个服务拖到超时。我写这篇指南的目标很直接帮你把“增删改查”这四个基本功练扎实再沿着索引、执行计划这条线把“高效查询”这件事彻底讲清楚。不管你是刚接触MySQL的学生还是写了两年代码但没深究过SQL细节的后端这篇内容都值得读完。我不会讲那些大而全的官方手册而是把我实际开发里反复用到的建表逻辑、SQL写法、调优手段、安装部署坑点串起来做成一份能直接拿来用的实战笔记。看完之后你至少能回答三个问题一条SQL是怎么在MySQL里跑起来的为什么有的SQL快、有的SQL慢线上环境出了问题应该从哪些地方开始排查1. 先搞懂增删改查再谈高效查询1.1 增删改查背后到底在操作什么很多初学者把INSERT、SELECT、UPDATE、DELETE当成四个孤立命令这恰恰是后续优化做不好的根源。增删改查其实代表了一套完整的数据生命周期新增一条数据、找到它、修改它、删除它。表面上是操作“行”但底层是InnoDB存储引擎在操作“页”和“索引”。我用一个生活类比帮你理解。你的衣柜就是一张表每件衣服是一行数据衣柜里的分隔区和标签就是索引。整理衣柜时你希望快速找到某件衣服而不是把整个衣柜翻一遍数据库也一样全表扫描就像把所有衣服一件件拿出来看数据量小的时候没什么感觉一旦到了百万行就会明显变慢。高效查询的核心不是SQL写得多花哨而是让MySQL尽量少读数据页。索引存在的意义就是减少扫描的数据量。明白这一点你才会理解为什么CREATE INDEX能救命为什么WHERE条件写不好会全表扫描为什么DELETE大表数据能把数据库拖垮。这些问题不是靠背几条命令能解决的而是需要你真正理解数据在磁盘上是怎么组织的。1.2 建表设计高效查询的第一道关卡优化查询最划算的时间点其实是在建表阶段。很多人在建表时图省事所有字段都用VARCHAR(255)主键也不管字符集默认latin1结果后面查询慢、连接乱码、索引失效全都来了。我建议你至少在MySQL 8.0下用下面这种姿势建表CREATE TABLE users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(64) NOT NULL COMMENT 用户名, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0正常 1禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username), KEY idx_status_create_time (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT用户表;这里面有几个点值得展开讲。主键建议用BIGINT自增而不是随机字符串。因为InnoDB的聚簇索引按照主键顺序组织数据自增主键能让插入尽量顺序写减少页分裂。UUID做主键如果顺序无序插入时随机写性能会差很多。字符集无脑选utf8mb4。它既兼容UTF-8又能存emoji还能避免索引长度超限的报错。MySQL 8.0默认的COLLATE是utf8mb4_0900_ai_ci如果还在用5.7的utf8mb4_general_ci也没关系但一定要保证同一个库统一。默认值要设置好不然每次插入都要额外写字段。比如status TINYINT NOT NULL DEFAULT 0这就是很多业务里默认状态的常规做法也是你搜索“mysql设置默认值为0”时最常见的场景。create_time用DEFAULT CURRENT_TIMESTAMP插入时不用手动维护时间非常省事。索引不要一上来就建一大堆。索引虽然能加速查询但每次INSERT、UPDATE、DELETE都要同步维护索引索引太多写性能必然下降。通常只要优先覆盖高频查询场景比如用户名查用户就建唯一索引状态和时间段组合查询就建复合索引。如果表结构设计得乱后面写再多优化SQL也无力回天。数据库设计有个原则先解决存储结构问题再解决查询效率问题。2. 增删改查的实操细节与常见坑2.1 SELECT别让“SELECT *”坑了你的查询SELECT是日常写最多的语句但很多人习惯性地SELECT *。这样做有两个问题第一如果表中字段很多尤其是TEXT、BLOB类型的大字段会把用不上的数据也查出来白白增加网络传输和内存开销第二SELECT *容易破坏覆盖索引本来索引里就已经有你需要的数据但因为你要求所有列MySQL就只能回表再去数据页拿完整行。来看一条比较完整的SELECT语句SELECT username, status FROM users WHERE status 0 GROUP BY status HAVING COUNT(*) 1 ORDER BY create_time DESC LIMIT 10;这条语句看起来很普通但它的执行顺序和你书写的顺序完全不一样。实际执行顺序大致是FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT也就是说MySQL先确定表再过滤行然后分组分组后再过滤最后才投影出你要的列接着排序最后取前10条。理解这个顺序很有用比如WHERE条件里能不能用SELECT里的别名答案是不能因为别名在WHERE之后才计算。又比如ORDER BY create_time能不能走索引如果WHERE里已经用到了create_time的范围且和索引顺序一致就有机会。写SELECT时还有几个实际经验只要业务需要的字段避免SELECT *。尽量把过滤力度大的条件放到前面虽然优化器会自动调整但逻辑上更清晰。如果你只关心有多少条用COUNT(*)不要查出一堆行再在代码里数。排序字段如果没建索引数据量大时会出现Using filesort这个后面会专门讲。2.2 INSERT批量插入与冲突处理入门阶段很多人是一条一条INSERT在循环里执行几千次性能极其拉胯。其实MySQL支持多值插入你完全可以一次写多行INSERT INTO users (username, status) VALUES (zhangsan, 0), (lisi, 0), (wangwu, 1);这样做的原因是减少SQL解析和网络往返。一次插入100行的开销远比100次单行插入小得多。我见过有人为了图方便在Java里for循环调用insert结果10万条数据插了十几分钟改成批量插入后不到几十秒就完成。如果你的业务要求“存在则更新不存在则插入”MySQL也给了一个很实用的语法INSERT INTO users (username, status) VALUES (zhangsan, 0) ON DUPLICATE KEY UPDATE status VALUES(status);这句话的意思是如果username唯一键冲突就执行后面的UPDATE。很多幂等操作都用这个写法。注意ON DUPLICATE KEY UPDATE依赖唯一索引或主键如果表上没有任何唯一约束它不会生效。插入数据时还有一个容易被忽略的点字段类型。比如一个VARCHAR字段你传入了数字MySQL会做隐式转换可能让本来能走索引的查询失效。插入时尽量保证类型匹配不要指望数据库自动帮你救场。2.3 UPDATE写WHERE条件前先冷静三秒要说生产事故排行榜UPDATE忘记WHERE一定名列前茅。一条不加WHERE的UPDATE会把整张表全部更新而且没有后悔药可吃除非你有备份。正确的写法很简单UPDATE users SET status 1 WHERE username zhangsan;但这里有个细节如果username不是索引列这条UPDATE会先全表扫描定位行再逐行更新。你以为只更新一条实际它把全表扫了一遍锁也加了全表那么多行。所以UPDATE的WHERE条件最好能用到主键或索引。我建议你打开MySQL的安全更新模式这样能拦下部分不带WHERE的误操作SET sql_safe_updates 1;开启后如果UPDATE或DELETE没有WHERE条件或者WHERE条件没有用到索引MySQL会直接拒绝执行。这个习惯一定要养成。再分享一个实际场景需要把一张表里“订单表中存在记录”的用户全部禁用很多人会先查出来再一条条更新其实可以用关联更新UPDATE users u JOIN orders o ON u.id o.user_id SET u.status 1 WHERE o.create_time 2024-01-01;这样的写法要留意外键和索引建议在orders.user_id和orders.create_time上建索引否则JOIN那一步就会全表扫照样慢。2.4 DELETE与TRUNCATE删数据要懂得轻重缓急很多新手分不清DELETE和TRUNCATE以为删数据都一样。实际上差别非常大。DELETE是DML逐行删除会写binlog支持按条件删支持事务回滚。TRUNCATE是DDL直接重建表速度极快但会重置自增ID且几乎不能回滚某些隔离级别下会有风险。清空一张日志表用TRUNCATE比DELETE快太多但想“删除状态为0的数据”这种需求只能DELETE。真正的大坑是“删除大部分数据”。假设你有一张1000万行的日志表要删除其中900万行直接用DELETE删会带来很大的锁压力、binlog压力和回滚段压力极容易把数据库拖死。这种场景我的做法是分批删DELETE FROM logs WHERE create_time 2024-01-01 ORDER BY id LIMIT 1000;改成在业务低峰期循环执行每删一批停几秒直到删完。或者干脆把表重命名创建一张新表再把要保留的数据INSERT进去。这种方式比大批量DELETE更稳。还有一个很容易被忽略的点DELETE不会释放磁盘空间。表文件仍然占用原来的大小因为高水位没有下降。如果你需要彻底瘦身后续得OPTIMIZE TABLE。对于“逻辑删除”即增加IS_DELETE字段平时查询加条件WHERE is_delete 0这是很常用的方案。代价是每个查询都要多一个过滤条件但只要索引设计合理影响不大。2.5 存储过程把增删改查封装起来看到“mysql声明存储过程”的热搜词说明很多人到入门后不久就想把SQL封装起来。存储过程确实能把一段复杂的业务逻辑放到数据库里执行减少应用和数据库的交互次数。下面是一个最基础的声明和调用DELIMITER // CREATE PROCEDURE get_user_by_id(IN p_id BIGINT, OUT p_username VARCHAR(64)) BEGIN SELECT username INTO p_username FROM users WHERE id p_id; END // DELIMITER ;调用CALL get_user_by_id(1, out); SELECT out;这里重点说下DELIMITER的作用。MySQL默认用分号作为语句分隔符但存储过程内部也有分号如果不临时把分隔符改成//MySQL会在第一条内部分号处误以为过程定义结束了导致语法错误。这是新手最容易踩的坑。存储过程还可以做异常捕获。比如事务里发生错误就回滚同时记录错误信息CREATE PROCEDURE update_user_status(IN p_id BIGINT) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; GET DIAGNOSTICS CONDITION 1 err_no MYSQL_ERRNO, err_msg MESSAGE_TEXT; SELECT err_no AS err_no, err_msg AS err_msg; END; START TRANSACTION; UPDATE users SET status 1 WHERE id p_id; COMMIT; END //但是我并不推荐在业务系统里大量使用存储过程。原因是它把业务逻辑塞进数据库导致版本管理、测试、横向扩展都变得困难。相对合理的使用场景是定时任务、报表统计、数据迁移、ETL等数据库侧工具链。普通Web项目还是把业务逻辑放在应用层更可控。3. 高效查询索引、执行计划与慢查询优化3.1 索引从书目录到B树搜索引擎为什么快因为建立了倒排索引。MySQL为什么能通过条件快速定位靠的是B树索引。理解索引不用啃算法书你只需要知道索引是有序保存的关键字结构MySQL通过它能把“扫描全表”降级为“按树路径查找”。我举一个例子。如果users表没有索引执行SELECT * FROM users WHERE username zhangsan;MySQL只能从上往下一条条比对这就是全表扫描。如果username上有唯一索引MySQL会沿着B树找复杂度大约是logN几十万行的表也就是几次IO就能定位到。创建索引的SQL很基础ALTER TABLE users ADD INDEX idx_status (status); ALTER TABLE users ADD INDEX idx_username (username);但我特别想提醒的是复合索引。比如查询条件是status和create_time你建两个单列索引MySQL最终一般只会选其中一个另一个用不上。而建一个复合索引idx_status_create_time(status, create_time)才能同时支持status过滤和create_time排序。复合索引有个核心规则叫最左前缀原则查询条件里必须包含索引最左边的列才能使用这个索引。比如idx_status_create_time可以支持status0也可以支持status0 ORDER BY create_time但单独用create_time条件时它就失效了。所以建索引前先盘点高频查询的WHERE模式优先建复合索引不要堆一堆单列索引。3.2 EXPLAIN让执行计划替你说话一条SQL慢你要先看它到底是怎么跑的。EXPLAIN是MySQL自带的执行计划分析工具直接在前面加EXPLAIN关键字即可EXPLAIN SELECT * FROM users WHERE username zhangsan;执行后你会看到一张表重点关注这几列type、key、rows、Extra。type是访问类型从好到差大致是system → const → eq_ref → ref → range → index → ALL如果看到ALL说明是全表扫描大概率需要加索引。看到index说明扫描了整棵索引树虽然比ALL好点但也不理想。看到ref或range说明定位到了一部分数据算正常。看到const说明直接命中主键或唯一索引这是最快的情况。key表示最终用了哪个索引。如果为NULL说明没用到索引。rows是预估扫描行数数字越大越危险。Extra里如果出现Using filesort或Using temporary说明排序和分组没走索引大数据量下会非常慢。我之前帮同事排查过一条慢SQLSELECT * FROM orders WHERE DATE(create_time) 2024-01-01;看起来很正常但EXPLAIN出来typeALLrows等于全表行数。原因是在create_time列上用了DATE函数导致索引失效。优化后改成范围查询SELECT * FROM orders WHERE create_time 2024-01-01 AND create_time 2024-01-02;这样既能走索引语义也完全一样。记住一句话别对索引列做计算别对索引列做隐式类型转换否则索引就白建了。3.3 慢查询日志与SQL调优实战只看EXPLAIN还不够你还需要知道系统里哪些SQL真的慢。MySQL提供了慢查询日志在my.cnf里配置slow_query_log ON slow_query_log_file /var/log/mysql/mysql-slow.log long_query_time 2设置超过2秒的SQL都会记录下来。接着你可以用mysqldumpslow分析mysqldumpslow -s at /var/log/mysql/mysql-slow.log按平均时间排序看看哪些SQL反复上榜这些就是你要优先优化的对象。另外线上出问题时要学会看实时连接。执行SHOW FULL PROCESSLIST;可以看到当前所有连接在执行什么SQL、跑了多久、卡在什么状态。如果某个连接长期处于Locked或者执行时间特别长可以直接KILL 12345;这个数字就是processlist里的ID。我曾经遇到过一条关联查询把大表扫了个遍导致其他请求全部排队就是靠SHOW FULL PROCESSLIST找到它并KILL掉的。慢SQL的常见套路我列一下LIKE %xxx这种前置模糊查询无法走索引。OR条件中只要有一个列没索引整个条件都可能扫表。使用函数或运算包裹索引列索引失效。隐式类型转换比如把VARCHAR列和数字比较索引失效。ORDER BY RAND()在大表上会生成临时表极其昂贵。遇到这些场景优先改SQL实在改不了再考虑加索引、改表结构或者使用全文索引、ES等外部方案。3.4 排序、分页与大数据量下的查询优化排序也是高频需求。ORDER BY create_time DESC如果create_time上有索引MySQL可以直接倒序扫描不需要额外排序。如果没索引就要在内存或磁盘里做filesort数据量大时很伤。分页是另一个重灾区。你可能写过SELECT * FROM orders ORDER BY id LIMIT 100000, 20;这句SQL意味着MySQL要扫描前10万条记录然后扔掉前10万条只返回最后20条。如果你的订单表有几百万行翻页越深越慢。一个经典的优化方案是“延迟关联”或“基于游标”。延迟关联写法SELECT o.* FROM orders o JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t ON o.id t.id;先用索引查出需要的id再回表拿完整数据而不是一开始就把所有字段查出来。游标式写法更适合App列表SELECT * FROM orders WHERE id 100000 ORDER BY id LIMIT 20;因为id是主键走索引且只扫描20行速度飞快。这就是为什么很多分页接口会要求前端传lastId而不是传page/pageSize。还有一点排序字段如果很复杂比如ORDER BY a DESC, b ASC索引得和排序方向保持一致否则也会filesort。虽然MySQL 8.0支持降序索引但大多数场景下多思考一下能不能用主键排序往往更简单。4. 实战环境安装、连接、部署与主从复制4.1 安装MySQL别在第一步就选错版本搜索“mysql下载官网”“mysql下载哪个版本”的人特别多。我的建议非常明确生产环境选MySQL 8.0社区版不要选最新开发版也不要再装5.7了除非你有老项目必须兼容。下载时认准MySQL Community Server别下成商业版。安装方式有几种。Windows下直接下载MSI安装包一路Next即可。Linux下用包管理器最省事。以CentOS系为例sudo yum install mysql-server sudo systemctl start mysqldUbuntu/Debian系sudo apt update sudo apt install mysql-server sudo systemctl start mysql如果你更喜欢容器化Docker一条命令就能起docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEtestdb \ mysql:8.0我推荐用Docker既能快速体验又不会把本机环境搞乱。注意-v挂载一个volume到/var/lib/mysql不然容器删了数据就丢了。安装完成后默认root账户出于安全考虑一般只允许localhost登录。如果你需要远程连接得创建一个允许远程访问的用户并授权CREATE USER app% IDENTIFIED BY Password123!; GRANT SELECT, INSERT, UPDATE, DELETE ON testdb.* TO app%; FLUSH PRIVILEGES;4.2 连接MySQL常见报错与SSL问题很多人在本地敲mysql -uroot -p结果报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)这个错误90%是因为MySQL服务根本没启动。先检查systemctl status mysqld # 或者 service mysql status没启动就启动。如果服务已经启动了还报socket路径不对多半是配置路径不一致。你可以用TCP方式连接避开socketmysql -h127.0.0.1 -P3306 -uroot -p还有一类高频问题是SSL连接错误。MySQL 8.0默认开启SSL某些客户端和服务端证书协商失败时会报错。如果你只是本地开发不需要加密连接可以在命令行加参数mysql -uroot -p --ssl-modeDISABLEDJava应用里对应的JDBC参数是useSSL和sslModejdbc:mysql://localhost:3306/testdb?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8这里要注意useSSL是老版Connector/J的参数sslMode是8.0新参数可以配合使用。如果你连接生产开启了SSL就要配置正确的CA证书否则会一直报警一堆验证失败。如果你用Navicat连接MySQL 8.0有时会提示“Authentication plugin caching_sha2_password cannot be loaded”这是因为8.0默认认证插件变了。解决方法要么升级Navicat要么把用户改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;4.3 数据库连接池与JDBC让程序稳定连接程序连接数据库如果每次请求都新建连接开销是很大的。连接池就是预先创建一批连接用的时候取用完归还。常见的连接池有HikariCP、Druid、C3P0。Spring Boot默认就带HikariCP你只需要在配置里设置几个参数。我一般这样配spring.datasource.hikari.initialSize5 spring.datasource.hikari.minimumIdle5 spring.datasource.hikari.maximumPoolSize20 spring.datasource.hikari.connectionTimeout30000参数的意义我简单说明initialSize是启动时建立的连接数minimumIdle是空闲时最少保留的连接数maximumPoolSize是最大连接数connectionTimeout是从池里获取连接的最大等待时间。配置太小高峰期拿不到连接配置太大数据库连接数会被打满。需要结合业务压测来决定。这里有一个常见误区连接池线程数不等于业务线程数也不是越大越好。MySQL默认max_connections是151如果你每个应用连50个三个应用就可能打满。遇到连接被拒先看SHOW VARIABLES LIKE max_connections; SHOW STATUS LIKE Threads_connected;如果Threads_connected长期贴近上限你就得考虑调服务端上限或减少应用连接。数据库连接泄漏也是个经典问题。用池子一定要确保try-with-resources或finally里执行close否则连接永远不归还最后连接池被耗尽服务假死。排查时可以执行SHOW FULL PROCESSLIST看有没有大量连接一直挂在那里Sleep状态很久且来自同一个应用IP。4.4 MySQL Workbench与Navicat图形化利器虽然命令行很酷但日常开发中图形化工具效率更高。MySQL自带的Workbench免费跨平台能看ER图、执行EXPLAIN、导入导出数据。Navicat功能更强可惜是商业软件但很多公司会买授权。Workbench的常用操作其实就几个新建连接、打开SQL编辑器、运行SQL、查看执行计划。运行一条SQL后如果发现慢点一下执行计划按钮它会以图形方式展示表访问顺序、索引使用情况。这个功能对初学EXPLAIN的人特别友好比在命令行看表格直观多了。Navicat里我要多说一句连接MySQL 8.0时在连接属性里有“使用SSL”选项开发环境直接关闭即可不然经常因为SSL握手失败连不上。如果你连接的是云数据库且必须启用SSL那就要按云厂商文档上传CA证书。4.5 主从复制与远程表同步从单机走向集群当单库扛不住读写压力时最常用的方案就是主从复制。主库负责写从库负责读读压力被分流。原理不复杂主库把变更记录写进binlog从库拉取binlog并写入自己的relay log然后回放执行。配置步骤大概是主库my.cnfserver-id1 log-binmysql-bin binlog_formatROW从库my.cnfserver-id2主库创建复制账号CREATE USER repl% IDENTIFIED BY repl_pass; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES;查看主库当前binlog位置SHOW MASTER STATUS;假设返回Filemysql-bin.000001Position154在从库执行CHANGE MASTER TO MASTER_HOST主库IP, MASTER_USERrepl, MASTER_PASSWORDrepl_pass, MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS154; START SLAVE;最后查看状态SHOW SLAVE STATUS\G只要Slave_IO_Running和Slave_SQL_Running都是Yes主从就建立起来了。如果其中一个不是Yes下方会有Last_IO_Error或Last_SQL_Error提示照着修即可。还有一个很常见的需求“把远程库的这张表同步到本地”。如果只是同步一张表最朴素的办法是mysqldumpmysqldump -h远程IP -uxxx -p testdb orders orders.sql mysql -h本地IP -uxxx -p testdb orders.sql如果希望本地每次查询都实时拉远程数据可以启用FEDERATED引擎。先确认MySQL编译了FEDERATEDSHOW ENGINES;然后本地建一张FEDERATED表映射到远程表CREATE TABLE remote_orders ( id BIGINT NOT NULL, order_no VARCHAR(64), create_time DATETIME ) ENGINEFEDERATED CONNECTIONmysql://repl:repl_pass远程IP:3306/testdb/orders;之后本地直接SELECT这张表MySQL会远程查询。不过FEDERATED引擎性能有限适合少量低频数据别指望它做复杂JOIN和大批量操作。4.6 容器化部署MySQLkubesphere与1Panel踩坑记现在很多团队用Kubernetes或轻量面板管理MySQL。在kubesphere上部署MySQL一般思路是先创建PVC持久化存储再部署Deployment挂载MySQL数据目录设置环境变量MYSQL_ROOT_PASSWORD然后暴露Service。如果遇到“1panel的mysql无权限”多半是root用户只允许本机连接进去改一下用户权限就好。进入容器docker exec -it mysql8 mysql -uroot -p然后执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY 你的密码; GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; FLUSH PRIVILEGES;如果连容器都启动失败很大概率是数据目录权限不对。比如挂载了宿主机目录到/var/lib/mysql宿主机目录属主不是mysqlMySQL没有写权限。解决办法是在宿主机上chown -R 999:999 /your/mysql/data这里的999是MySQL容器内用户ID不同镜像可能不同可以直接用docker exec进去再看。这类权限问题在容器部署中很常见不用慌先看日志docker logs mysql8日志会告诉你拒绝访问具体路径照着修就行。5. 常见问题排查与避坑清单5.1 高频报错速查表我把实际运维和开发里碰到最多的报错汇总成了一张表方便你直接对着查报错或现象常见原因处理思路ERROR 2002 (HY000) socket连接失败mysqld服务未启动或socket路径不对启动服务或用-h127.0.0.1 -P3306走TCP连接Access denied for user账号密码错误或host限制检查用户名密码用root授权对应hostAuthentication plugin cannot be loadedMySQL 8.0默认caching_sha2_password客户端太旧升级客户端或改成mysql_native_passwordSSL connection error客户端不支持或证书不匹配开发环境可加--ssl-modeDISABLEDLost connection to MySQL server网络超时、连接被kill或包过大调timeout、检查max_allowed_packetDeadlock found when trying to get lock事务互相等待行锁统一更新顺序缩小事务查看死锁日志Table doesnt exist大小写敏感导致表名不一致设置lower_case_table_names1命名统一小写中文乱码连接或表字符集不是utf8mb4SET NAMES utf8mb4改库表字符集这张表里的每一条我几乎都在真实环境见过。尤其是ERROR 2002新笔记每次提起都有人遇到Access denied和SSL error则是最常见的连库拦路虎。5.2 锁、事务与并发面试问的最多的一块高效查询不只是索引的事并发场景下还要懂锁和事务。面试时提到MySQL锁原理几乎必问InnoDB行锁、间隙锁、next-key lock、共享锁、排他锁。简单理解共享锁是多个事务可以同时持有同一行锁做读操作排他锁是某个事务独占这行做写操作。正常UPDATE、DELETE、INSERT会加排他锁SELECT默认不加锁但可以手动加SELECT * FROM users WHERE id 1 FOR UPDATE;这种“悲观锁”常用于先查询再更新的业务但用多了会拖慢并发。死锁是大家最头疼的问题。比如事务A更新了id1再更新id2事务B更新了id2再更新id1。两个事务在对方等锁形成死锁。MySQL会检测并牺牲其中一个事务回滚但业务侧会看到Deadlock错误。我的建议是多个事务访问多条记录时约定相同的顺序比如都先id小的。事务尽量短避免在事务里做慢查询、外部接口调用。合理设置innodb_lock_wait_timeout默认50秒超时自动放弃。出现死锁后去查SHOW ENGINE INNODB STATUS里的LATEST DETECTED DEADLOCK定位两条互相竞争SQL。MVCC也是InnoDB并发读的核心它让普通SELECT走快照读不加锁也能保证可重复读。这些概念如果光背不实践是记不住的建议你在本地开两个MySQL会话一个事务里UPDATE另一个SELECT或UPDATE观察阻塞行为印象会非常深。5.3 我的几条实战经验最后把我这些年在项目里攒下来的经验分享给你也算是一份避坑清单。第一任何不带WHERE的UPDATE和DELETE执行前必须双人复核。我见过太多被这条坑的生产事故别拿奖金赌数据库备份。第二建索引不是拍脑袋而是先写候选SQL再看EXPLAIN。你能用一条复合索引满足三个查询就绝不建三个单列索引。第三慢查询日志要开着哪怕只记录超过2秒的SQL。日志是白盒不看慢查询日志就优化SQL就像蒙着眼睛修车。第四生产环境不要直接跑ALTER TABLE、OPTIMIZE TABLE。表数据量大时这些操作会锁表或产生大量IO最好用在线DDL工具或者选低峰期分批处理。第五不要长时间开着事务。哪怕不执行SQL一个长事务也会占用连接、持有快照、阻塞其他事务。代码里务必及时COMMIT或ROLLBACK。带过不少新同事后我发现很多人SQL写得快但一遇到慢查询就懵。我自己踩过最惨的一次是对着一个三百万行的订单表做了一条没索引的关联查询差点把主库拖到宕机。从那以后我写每个查询都会先想索引平时开发也开着慢查询日志。做MySQL开发没有捷径多踩坑、多看执行计划、多看看慢查询日志你会感谢现在这个愿意动手的自己。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑