作者小头像 Lv.1
31 成长值

个人介绍

这个人很懒,什么都没有留下

感兴趣或擅长的领域

暂无数据
个人勋章
  • 活跃之星
成长雷达
10
21
0
0
0

个人资料

个人介绍

这个人很懒,什么都没有留下

感兴趣或擅长的领域

暂无数据

达成规则

他的回复:
刷脏页有四个触发源,理解了这四个,卡顿的原因基本就浮出来了。1. 四条刷脏路径redo log 空间不够:redo 是环形写的,checkpoint 位置之前的脏页必须先落盘才能复用。这是最急的一种,一旦逼近就强制推进 checkpoint,刷脏速度直接决定能不能继续写。脏页比例超过阈值:innodb_max_dirty_pages_pct(5.7 默认 75,8.0 默认 90)触发后台 page cleaner 提前刷。LRU 淘汰:要读的新页在 free list 里没有了,只能从 LRU 尾部淘汰,淘汰脏页前必须先刷盘。正常后台刷:page cleaner 线程按 innodb_io_capacity 的节奏匀速刷,这条不卡业务。2. 为什么会"卡"MySQL 的刷脏是"单页刷"而不是"批量刷",而且用户线程在发现脏页太多、需要腾出位置时会亲自参与刷脏(帮 page cleaner 一起刷一两页)。一旦进入这个状态,原来 0.1ms 的写操作可能变成几毫秒——表现在业务侧就是偶发的 P99 尖刺,日志里什么错都没有。判断办法是看几个计数器,别靠猜:SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_free';SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_wait_free'; -- 大于 0 就是在等空闲页SHOW GLOBAL STATUS LIKE 'Innodb_data_pending_fsyncs';Innodb_buffer_pool_wait_free 持续增长是最直接的证据:说明查询要读的页没有位置可放,在等刷脏。3. 优化顺序(按性价比)先看 buffer pool 大小。innodb_buffer_pool_size 太小,工作集放不下,脏页永远在临界点上,加内存是最有效的一步(一般给到物理内存的 60%~75%)。再调刷脏速度:机械盘把 innodb_io_capacity 调低(200 左右)避免 IO 打满,NVMe 调到几千甚至上万,让它有能力一次刷干净;innodb_io_capacity_max 一般是它的 2 倍。调低 innodb_max_dirty_pages_pct(比如 8.0 下从 90 降到 60~70),让后台更早开始刷,用"平时多刷一点"换"尖峰不卡"。innodb_flush_neighbors 在 SSD 上设为 0,避免不必要的邻页连带刷。4. 一个常见误判把大事务拆小确实能缓解,但根本原因常常是"写入突增 + buffer pool 太小"。如果监控里 Innodb_buffer_pool_pages_dirty 在尖峰时刻接近总页数的 70% 以上、同时 wait_free 开始增长,那就是容量问题,调参数只能拖延,加内存或拆库才解决。
发布时间 2026/10/27 22:44:24 最后回复 yd_232401777 2026/11/03 08:49:37 版块 数据库
44 9 0
他的回复:
先给结论:RR 下 InnoDB 用的是两套机制,各管一半——快照读靠 MVCC,当前读靠间隙锁(next-key lock)。所以严格来说 RR 并没有"完全消灭幻读",它消灭的是特定路径上的幻读。一、快照读:MVCC + Read View,根本不看新插入的行事务第一次快照读时生成一个 Read View,里面记着当时活跃的事务 id 列表(m_ids)、最小活跃 id(min_trx_id)、下一个待分配 id(max_trx_id)。之后每一行数据都拿自己的 DB_TRX_ID 去比:比 min_trx_id 小 → 已提交,可见;比 max_trx_id 大 → Read View 之后才开的事务,不可见,顺着 undo log 找上一版本;落在中间 → 看是否在 m_ids 里,在就不可见。关键是 Read View 在 RR 下整个事务只生成一次(RC 下每条语句都重新生成),所以后插入的行 DB_TRX_ID 更大,天然不可见。第一次 SELECT 看到 3 行,后面无论别人插入多少,还是 3 行——幻读在快照读下不存在。二、当前读:next-key lock 锁住间隙SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT 都是当前读,必须读最新版本,MVCC 帮不上忙,靠锁。RR 下行锁实际是 next-key lock = 记录锁 + 间隙锁(gap lock),锁的是"某个索引值以及它前面的区间"。举例,表里有 id = 1、5、10:-- 事务ABEGIN;SELECT * FROM t WHERE id BETWEEN 1 AND 5 FOR UPDATE;-- 锁住 (-∞,1]、(1,5]、(5,10) 这三段-- 事务BINSERT INTO t VALUES (3); -- 阻塞,落在 (1,5] 间隙里INSERT INTO t VALUES (7); -- 不阻塞就是 bug,7 在 (5,10) 里,同样阻塞间隙锁之间是兼容的(两个事务可以同时持有一段间隙锁),它只和"插入意向锁"冲突,作用就是挡住 INSERT。三、那 RR 什么时候还会幻读?经典场景:事务 A 先快照读,事务 B 插入并提交,事务 A 再当前读(UPDATE 或 FOR UPDATE),会看到新行。因为当前读不看 Read View。反过来的顺序(先当前读再快照读)不会。-- A -- BSELECT * FROM t WHERE id=100; -- 快照读,0 行 INSERT INTO t VALUES (100); COMMIT;UPDATE t SET c=1 WHERE id=100; -- 当前读,命中 1 行,然后 SELECT 也能看到这是 MVCC 与锁两套机制切换时留下的一致性缺口,不是 bug。要彻底避免,业务上给"读—判断—写"这段加显式锁(FOR UPDATE),或者用唯一索引把并发插入挡在约束层。四、实践上的两个提醒间隙锁只在 RR(及更严格)下生效,改成 RC 就没有 gap lock 了,幻读完全交给业务处理——这也是很多互联网业务选 RC 的原因:锁范围小、死锁概率低;范围更新要走索引。走全表扫描的 UPDATE ... WHERE 非索引列 会把整张表的所有间隙都锁上,表现就是"更新一行,全表插入全阻塞"。
他的回复:
共享锁和排他锁是行级的读写锁,意向锁是"表级"的标记位——它不锁数据,只用来让表锁能快速判断"这张表里有没有行锁",避免加表锁时逐行扫描。1. 共享锁(S)与排他锁(X)SELECT ... LOCK IN SHARE MODE(8.0 写法 FOR SHARE)加 S 锁,多个事务可以同时持有同一行的 S 锁,但不能有别人在这行上加 X 锁;SELECT ... FOR UPDATE、UPDATE、DELETE 加 X 锁,互斥一切;普通 SELECT 不加锁,走 MVCC 一致性读,这也是最常见的一层误解——不是"读就加 S 锁"。2. 意向锁(IS / IX)解决的具体问题假设事务 A 已经锁住了表 T 的第 1000 行。此时事务 B 想执行 LOCK TABLES T WRITE(表级 X 锁),数据库必须知道"表里到底有没有行被锁住"。没有意向锁的话,只能逐行检查,一张千万行的表根本没法看。有了意向锁,规则变成:事务在给某行加 S 锁之前,先给这张表加 IS;加 X 锁之前,先加 IX;表级锁的兼容判断只看这两个标记:IS 和 IX 之间互相兼容(不同行可以并存),但它们和表级 S/X 冲突;于是 B 加表锁时,只要看到表上有 IX 标记就立刻知道"有行锁",直接等待,一次判断搞定。所以意向锁是由存储引擎自动加的,用户无法显式指定,你在 SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 段里能看到 lock mode IX 这类记录。3. 两个容易搞混的东西意向锁不等于元数据锁(MDL)。MDL 是 Server 层为了 DDL 与 DML 互斥加的,跟存储引擎的意向锁是两套机制。alter table 卡住、后面一堆查询跟着排队,那是 MDL 在等,不是意向锁。间隙锁也会带 IX/IS。RR 隔离级别下范围更新会加 next-key lock(行锁 + 间隙锁),对应的意向锁一样存在,所以"表上有 IX"不等于"某一行被锁死",具体冲突还是要看 performance_schema.data_locks。4. 排查建议:8.0 直接查 SELECT * FROM performance_schema.data_locks,LOCK_TYPE=TABLE 且 LOCK_MODE=IX/IS 的就是意向锁,LOCK_TYPE=RECORD 的才是真正的行锁和间隙锁。看死锁日志时先分清这两类,能少走很多弯路。
发布时间 2026/10/27 22:45:51 最后回复 yd_232401777 2026/11/04 08:45:34 版块 数据库
71 8 0
他的回复:
最左前缀原则一句话就能说清:B+ 树是按索引定义的列顺序逐列排序的,所以只能"从最左边开始连续匹配",跳列就断档。建了 idx(a, b, c),树的排序方式是先按 a 有序,a 相同再按 b 有序,a、b 相同再按 c 有序。所以:WHERE a = 1、a = 1 AND b = 2、a = 1 AND b = 2 AND c = 3 都能定位;WHERE b = 2 用不上,因为 b 只在同一个 a 内部有序,全局是乱的;WHERE a = 1 AND c = 3 只用得到 a,c 退化为回表后过滤(Extra 里通常看不到 Using index condition 的定位效果)。你问的重点——范围查询之后还能不能用:能参与索引,但只能做过滤,不能再缩小扫描范围。-- idx(a, b, c)WHERE a > 1 AND b = 2 AND c = 3这里 a > 1 是范围条件,a 一旦是范围,b 和 c 在树里就不再全局有序(不同 a 值下的 b 各自排序,跨 a 就断),所以 MySQL 只能把 a > 1 作为扫描边界,b、c 在扫出来的每一行上逐行判断。EXPLAIN 里 key_len 只算到 a 的长度,Extra 会看到 Using index condition(索引条件下推,减少回表次数,但没减少扫描范围)。所以优化方向上有个固定套路:等值列在前,范围列在后。上面那条改成 idx(b, a) 或 (b, c, a),才能让 b 的等值定位生效,把扫描范围压到最小。另外两个常被忽略的点:ORDER BY 也用同一套规则。索引列顺序与 order by 一致、且排序方向一致时才能免排序;中间插一个范围条件,后面的列排序就用不上索引了,会多出 Using filesort。WHERE a IN (1,2,3) AND b = 2 里的 IN 在优化器看来近似多个等值区间,b 仍有机会用上;但 a > 1、a BETWEEN、a LIKE 'x%' 这类连续范围就会截断后面的列。验证方法只有一个:加列改列之后跑 EXPLAIN,看 key、key_len、rows 三个值的变化,别靠背结论。
发布时间 2026/10/30 07:36:13 最后回复 yd_229207802 2026/11/02 12:04:22 版块 开发者空间
21 7 0
他的回复:
这个问题下面已经有不少方法论了,我补一点能直接落地的算法,因为"按业务画像选系列"之后,最难的一步其实是定核数和内存的数。一、先把需求算成一个数,而不是凭感觉。压测一轮单请求的开销,拿到两个值:单请求 CPU 时间(毫秒)和峰值常驻内存(RSS,MB)。然后:所需核数 ≈ 峰值QPS × 单请求CPU时间(ms) / 1000 × 1.3所需内存 ≈ 单实例RSS × 实例数 + 页缓存余量系数 1.3 是给突发和 GC 留的余量,不是给"以后再说"留的。算出来 2 核够,就先买 2 核,别一步到 8 核。二、用三个指标决定"要不要升",别用感觉。上线后盯一到两周:CPU 使用率持续 15 分钟以上超过 70%、内存 RSS 长期贴着规格上限(此时 dmesg | grep -i oom 通常已经有记录)、磁盘 await 明显偏高。三个里中一个就升,都不中就别动。云监控的 1 分钟粒度曲线比"某个瞬间 top 看了一眼"可靠得多。三、成本上真正差钱的往往不是规格,是计费方式和周边资源。长期稳定跑的业务走包年包月,短期和弹性场景走按需;带宽按 95 峰值带宽或按流量二选一,出口流量大的业务按流量经常更便宜。另外系统盘默认 40G 一般够用,但日志盘一定单独挂一块,否则日志写满系统盘导致 SSH 都上不去的场面很难受。四、突发性能型只适合开发和测试。它的 CPU 是基线加积分模型:平时攒积分、忙时消耗,积分耗完会被限频到基线,表现就是"跑压测中途 QPS 突然掉一半"。生产环境如果负载有周期性尖峰,宁可买小一号的通用型,也别用突发性能型扛。五、横向还是纵向,看你的应用能不能多实例。能无状态多实例的,2 核 4G × 2 比 4 核 8G × 1 更划算,也天然有冗余;单机数据库这类不能拆的,只能纵向升配,注意升配要关机重启(数据不丢,但会中断几分钟),生产要排到维护窗口。一句话:先用压测把需求算成核数和内存数,再用监控决定什么时候加,最后在计费方式和带宽磁盘上省钱——顺序反过来就容易一开始买大、后面又不敢降。
他的回复:
你只要调用接口那一刻的瞬时值,那就别在"平均"上纠结,把统计口径压成一个采样点就行。几条实操建议:1. 现成的使用率字段可以先找,不必自己算。运维面报表里有"平均使用率"这一列,说明前端一定调了某个接口拿到它。F12 打开 Network,过滤 stat-hypervisor,把报表页那次请求的 metrics 数组原样抄下来,字段名和 aggType 一次对齐,比翻文档猜字段名快得多。如果里面有 metrics.memoryUsage 这类字段,直接取即可。2. 没有现成字段就用同一时刻的 used / total 现算。你贴的请求里只有 metrics.memory、metrics.memoryUsed、metrics.memoryLeft,这三个都是绝对量,接口不会替你除出百分比。计算方法是在同一个时间戳上做聚合后再相除:memory_usage_pct = sum(metrics.memoryUsed) / sum(metrics.memory) * 100cpu_usage_pct = sum(metrics.vcpusUsed) / sum(metrics.vcpus) * 100关键在于两个 sum 必须来自同一个时间点。只要查询窗口是一个采样点,aggType: sum 出来的就是那一刻的合计值,除完就是瞬时使用率。3. 想要瞬时值,就把时间窗口收窄到一个采集粒度。比如起止时间只差 1 分钟(等于最小采集粒度),或者干脆起止时间填相同值。窗口一旦跨小时,sum 变成周期累计、avg 变成周期均值,就不再是瞬时值了。这也是为什么报表里的平均值你复现不出来——两者的窗口不是一个量级。4. 一个容易混的坑:memoryLeft 是分配口径的剩余量,不是监控口径的空闲内存。所以 1 - memoryLeft/memory 得到的是分配率,跟上面算出来的实际使用率是两回事,别拿它当使用率用。截图里 CPU 分配率 28.40%、使用率 9.47% 就是这种差异的典型表现。5. 校验一次再接入。取完瞬时值后,去监控侧按同一台主机、同一时刻拉一条 CPU/内存使用率曲线做对比,两条数应该贴得很近。差得远通常是三件事之一:区域或主机组标识没带全、时间窗口跨了多个采样点、或者把已关机、无监控数据的主机按 0 计入了分母。结论:瞬时值最好取——单点窗口 + sum 聚合 + used/total 相除,三个条件同时满足就得到你要的那个数,跟报表里那个"平均使用率"本来就不是同一个指标,对不上是正常的。
发布时间 2026/10/29 07:36:37 最后回复 komari 2026/10/30 07:03:40 版块 开发者空间
36 9 0
他的回复:
OBS 的坑基本集中在两件事上:用文件系统的思维去用它,以及没算清楚计费项。我按踩过的顺序列一下。1. 桶名和区域创建时就定死桶名全局唯一且创建后不能改,区域也不能迁移。同区域内网访问走 obs..myhuaweicloud.com 不产生流量费,跨区或者走外网域名就要算流量,很多人是账单出来才发现。2. 目录是假的OBS 只有扁平 key,a/b/c.txt 里的斜杠只是前缀。列表要配 prefix 加 delimiter 才有目录效果;rename 一个目录实际上是逐个对象 copy 加 delete,对象多了既慢又按请求数计费,大批量整理建议用生命周期规则或 obsutil,别自己写循环。3. 权限别图省事开公共读IAM 策略、桶策略、ACL 三套权限叠加取并集生效,经常出现桶策略禁了但对象 ACL 还开着公共读。需要临时分享就给预签名 URL(带过期时间),这是最不容易出事的方式。4. 分段上传的碎片会一直占空间大文件分段上传中断后,已上传的分段既不可见又继续计费。建议在桶上配一条 AbortIncompleteMultipartUpload 生命周期规则,比如 7 天后清理,否则账上会莫名多出容量。5. 覆盖写不留历史同名对象直接覆盖,误传只能重来。重要桶开版本控制,代价是存储量上涨,用生命周期规则把非当前版本定期转低频或删掉。6. 计费项不止存储请求次数(PUT、GET、LIST 分别计价)、外网下行流量、跨区复制流量都要算。GET 请求量大的场景(小图、切片文件),请求费可能比存储费还高,要么前置 CDN,要么把零碎小对象合并。最后一条:先把 SDK 或 obsutil 的内网 endpoint 配好再写业务代码,别等上线才发现流量在走外网。
他的回复:
这两类东西的分界线是 SQL 在哪儿被改写:proxy 在应用外面改,客户端分片在应用里面改。中间件代理(MyCat、ShardingSphere-Proxy)独立进程,伪装成 MySQL 协议服务端,应用把它当单库连;好处是对应用完全透明,PHP、Python、Go、老系统都能接,语言无关;代价是多一跳网络、多一层连接数(应用连接数乘上分片数很容易把后端打满)、代理自身要运维和做主备,SQL 兼容性也永远差一截,复杂子查询、跨库 join、存储过程经常不支持或被改写得很难看。客户端分片(Sharding-JDBC)是个 jar,跑在应用进程里,SQL 解析、路由、结果归并都在本地完成;没有额外网络跳,延迟最低,链路排查也简单,慢在哪一环看应用日志就行;缺点是绑定 Java 生态,每个服务都要引依赖、维护分片配置,版本升级要跟着业务一起发;跨语言团队没法共用一套分片规则。选型上我的判断顺序是:多语言、存量系统不能改代码、或者需要给 DBA 一个统一入口,用 proxy;纯 Java 微服务、对 P99 敏感、分片规则稳定,用 Sharding-JDBC;两边都要,可以用 ShardingSphere 的 JDBC + Proxy 混合部署共享同一套分片配置,但要接受两套运维成本。还有两个共性坑:一是这类中间件都不解决跨库事务,分布式事务要靠本地消息表、Seata 或者业务上做最终一致;二是分片键一旦选错几乎没法改,扩容要重新洗数据,所以选之前先把查询模式摸清楚,别按主键随便分。
他的回复:
索引失效这件事,看 EXPLAIN 比背结论有用,我一般就盯几个点:type 变成 ALL、key 为 NULL、rows 接近全表、Extra 里出现 Using filesort 或 Using temporary。1. 隐式类型转换列是 varchar,条件里写数字:WHERE order_no = 12345,MySQL 会把列转成数字比较,索引直接废掉。字符集不同的表做 join 也一样(utf8 对 utf8mb4),会让驱动表选错、被驱动表走全表。2. 列上做函数或运算WHERE DATE(create_time) = '2026-10-29'、WHERE id + 1 = 100 都是把索引列包起来了。改成范围:WHERE create_time >= '2026-10-29 00:00:00' AND create_time '2026-10-30 00:00:00'8.0 有函数索引可以救,但能改 SQL 就别加索引。3. 模糊查询LIKE '%abc' 和 LIKE '%abc%' 都用不上 B+ 树,只有 LIKE 'abc%' 能走范围扫描。前缀不确定的搜索需求,老实上全文索引或搜索引擎。4. 组合索引的最左前缀断裂建的是 (a, b, c),查询只带 b 或 a OR c 就断在最左边。另外范围条件之后的那一列只能做过滤不能做定位,所以 (a, b) 上 a > 1 AND b = 2 只能用到 a。5. OR 与否定条件WHERE a = 1 OR b = 2,只要 b 没索引整条就退化成全表;!=、NOT IN 在选择性差的时候优化器会直接放弃索引,这不是 bug,是代价估算的结果。最后一句:优化器不选索引不等于索引失效,有时候全表扫确实更快(比如要取 30% 以上的行)。看 EXPLAIN 的 rows 和 filtered 再下结论。