<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Exit 的笔记 · TIL</title><description>当下学到的小知识点。</description><link>https://exwer-blog.pages.dev/</link><item><title>旁路缓存：删缓存失败了怎么办</title><link>https://exwer-blog.pages.dev/til/cache-aside-consistency/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/cache-aside-consistency/</guid><pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;旁路缓存（Cache-Aside）用人话说：Redis 和数据库互不认识，全靠你的代码两边跑腿。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读&lt;/strong&gt;：先查缓存，没有再去库里查，顺手放进缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写&lt;/strong&gt;：只改库，然后把缓存里那份旧的删掉，等下次有人读再重新放。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两种失败的后果不一样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读库成功、写缓存失败&lt;/strong&gt;：没关系，缓存还是空的，下次再查库就行。记个日志，别报错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改库成功、删缓存失败&lt;/strong&gt;：缓存里留着脏数据，要兜底。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;兜底一般分三层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当场重试几次。&lt;/li&gt;
&lt;li&gt;还失败就扔进消息队列，或者订阅 binlog（比如 Canal），异步重试直到删掉。&lt;/li&gt;
&lt;li&gt;所有缓存都设过期时间，最坏情况下也会自己失效。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为什么是「删缓存」而不是「更新缓存」？两个并发写可能以相反的顺序更新缓存，留下旧值。删掉、等下次读时再填，问题就小得多。&lt;/p&gt;</content:encoded><category>Redis</category><category>缓存</category></item><item><title>Lease 模式：给回填缓存发一张许可证</title><link>https://exwer-blog.pages.dev/til/cache-lease/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/cache-lease/</guid><pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;旁路缓存有个经典竞态：A 查库拿到旧值，还没写进缓存；这时 B 改了库、删了缓存；A 再把旧值写进去。缓存里就是脏的了。&lt;/p&gt;
&lt;p&gt;Lease 的做法（来自 Facebook 的论文 &lt;em&gt;Scaling Memcache at Facebook&lt;/em&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存未命中时，发一张绑在这个 key 上的短时 &lt;strong&gt;token&lt;/strong&gt;，回填时必须带上它。&lt;/li&gt;
&lt;li&gt;中途有人删除这个 key，token 随之作废，带着旧 token 的回填会被拒绝。&lt;/li&gt;
&lt;li&gt;同一段时间里只发一张 token，没领到的人原地等一会儿，防止一千个请求同时冲到数据库（缓存击穿）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打个比方：给去仓库抄账本的人发一张「抄写许可证」。老板改账本、撕掉便签时，顺手把许可证作废；拿着旧数据慢慢走回来的人，验票不过，不准往便签上写。&lt;/p&gt;
&lt;p&gt;Redis 没有现成的 lease 命令，可以用一个 lease key 加 Lua 脚本近似实现。&lt;/p&gt;</content:encoded><category>Redis</category><category>缓存</category></item><item><title>MVCC 和 WAL 不是一层的东西</title><link>https://exwer-blog.pages.dev/til/db-mvcc-vs-wal/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-mvcc-vs-wal/</guid><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;两个缩写经常一起出现，但解决的是 ACID 里不同的字母：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;MVCC&lt;/th&gt;&lt;th&gt;WAL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;全称&lt;/td&gt;&lt;td&gt;多版本并发控制&lt;/td&gt;&lt;td&gt;预写日志&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;做法&lt;/td&gt;&lt;td&gt;改数据时留下旧版本，读事务读自己能看见的那一版&lt;/td&gt;&lt;td&gt;先把改动顺序追加到日志并刷盘，数据页稍后再写&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;解决&lt;/td&gt;&lt;td&gt;I（隔离）：读写不互相阻塞&lt;/td&gt;&lt;td&gt;D（持久）：崩溃后重放日志；顺带把随机写变成顺序写&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;InnoDB 里&lt;/td&gt;&lt;td&gt;undo 版本链 + Read View&lt;/td&gt;&lt;td&gt;redo log&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;一个管并发时谁能看见什么，一个管崩溃后怎么恢复。&lt;/p&gt;
&lt;p&gt;概念各家都有，实现不一样：PostgreSQL 没有 undo 版本链，新旧版本都留在表里，靠 VACUUM 清理；它的快照（xmin / xmax / xip）和 InnoDB 的 Read View 几乎能一一对上。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>改表的字符集，DEFAULT CHARACTER SET 改不动老列</title><link>https://exwer-blog.pages.dev/til/mysql-change-charset/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-change-charset/</guid><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 只改表的默认值：以后新加的列用 utf8mb4，已有的列不动&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; CHARACTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; utf8mb4;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 把已有列和数据都转过去&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t CONVERT &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; CHARACTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; utf8mb4;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 改单列的类型&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;MODIFY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; COLUMN v &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BIGINT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;后两种要改每一行的数据，只能走 COPY，期间不能写。&lt;/p&gt;
&lt;p&gt;有个例外：VARCHAR 扩长度，&lt;strong&gt;只要长度前缀的字节数不变&lt;/strong&gt;，就能 INPLACE。我在 utf8mb4 下试的：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;MODIFY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALGORITHM=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;INPLACE;  &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 50 → 60：成功&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;MODIFY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;70&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALGORITHM=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;INPLACE;  &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 50 → 70：报错&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;utf8mb4 一个字符最多 4 字节。VARCHAR(63) 以内最多 252 字节，长度前缀 1 字节；到 VARCHAR(64) 就超过 255 字节，前缀要变成 2 字节，行格式变了，只能 COPY。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>MySQL 8.0.18 的 hash join，救的是没索引的 JOIN</title><link>https://exwer-blog.pages.dev/til/mysql-hash-join/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-hash-join/</guid><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JOIN 本质上是嵌套循环：外层的驱动表每拿一行，就去被驱动表里找匹配。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;被驱动表的连接列&lt;strong&gt;有索引&lt;/strong&gt;：每次是一次索引查找，本来就不慢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没索引&lt;/strong&gt;：8.0.18 之前只能走 BNL，把一批驱动表的行放进 join buffer，再扫被驱动表两两比较，约 M × N 次。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;hash join 分两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构建&lt;/strong&gt;：把小表按连接列建一张内存哈希表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;探测&lt;/strong&gt;：扫一遍大表，每行算哈希去表里找。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;两张表各扫一遍，约 M + N。内存放不下时按哈希分片落盘，再一片片处理。&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;EXPLAIN FORMAT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;TREE &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; o &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; m &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; m&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; o&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;member_code&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- -&gt; Inner hash join (o.member_code = m.code)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;--     -&gt; Table scan on o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;--     -&gt; Hash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;--         -&gt; Table scan on m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;版本：8.0.18 只支持内连接等值，8.0.20 起外连接、半连接也用它，BNL 被删掉。只适合等值连接。&lt;/p&gt;
&lt;p&gt;顺带一个常被误解的点：「小表驱动大表」比的是&lt;strong&gt;过滤后&lt;/strong&gt;参与连接的行数，不是表的总行数。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>10 行的表、秒级的 INSTANT 加列，也能把服务堵死</title><link>https://exwer-blog.pages.dev/til/mysql-instant-can-block/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-instant-can-block/</guid><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; config &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; COLUMN remark &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALGORITHM=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;INSTANT;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;表只有 10 行，INSTANT 也只改数据字典、不碰数据，结果服务还是开始报超时。&lt;/p&gt;
&lt;p&gt;因为&lt;strong&gt;改表快不快&lt;/strong&gt;和&lt;strong&gt;拿锁要不要排队&lt;/strong&gt;是两回事。INSTANT 加列拿 MDL 的过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开头拿 SHARED_UPGRADABLE，不挡读写&lt;/li&gt;
&lt;li&gt;改数据字典，毫秒级&lt;/li&gt;
&lt;li&gt;提交时升级成 EXCLUSIVE，拿到后立刻改完立刻放&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;只要有一个没提交的事务占着读锁，第 3 步就要排队。它排在队头时，后面所有新查询都排在它后面，和表多大无关。&lt;/p&gt;
&lt;p&gt;对比 INPLACE（比如在线加索引）：SU → 短暂 X → 降回 SU 长时间执行（期间别人照常读写）→ 提交时再升 X。INSTANT 没有中间那段长时间执行，但头尾要锁的问题一样存在。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>Read View 怎么判断一个版本能不能看</title><link>https://exwer-blog.pages.dev/til/mysql-read-view/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-read-view/</guid><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;事务拍快照时，InnoDB 记下四个值：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;字段&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;m_ids&lt;/code&gt;&lt;/td&gt;&lt;td&gt;拍快照时还没提交的读写事务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;min_trx_id&lt;/code&gt;&lt;/td&gt;&lt;td&gt;m_ids 里最小的&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;max_trx_id&lt;/code&gt;&lt;/td&gt;&lt;td&gt;下一个要分配的事务 id&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;creator_trx_id&lt;/code&gt;&lt;/td&gt;&lt;td&gt;自己&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;读一行时，从最新版本开始，看它的 trx_id：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;等于 &lt;code&gt;creator_trx_id&lt;/code&gt;：自己改的，&lt;strong&gt;可见&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;小于 &lt;code&gt;min_trx_id&lt;/code&gt;：拍快照前就提交了，&lt;strong&gt;可见&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;大于等于 &lt;code&gt;max_trx_id&lt;/code&gt;：拍快照后才开始的事务，&lt;strong&gt;不可见&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在中间：查 &lt;code&gt;m_ids&lt;/code&gt;，在里面就是当时还没提交，&lt;strong&gt;不可见&lt;/strong&gt;，否则可见&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不可见就沿 &lt;code&gt;DB_ROLL_PTR&lt;/code&gt; 去 undo 里找上一个版本，直到找到第一个可见的。&lt;/p&gt;
&lt;p&gt;RR 整个事务只拍一张快照，RC 每条语句拍一张，可重复读和读已提交的区别就从这里来。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>脏读、不可重复读、幻读，一句话分清</title><link>https://exwer-blog.pages.dev/til/db-dirty-read-vs-phantom/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-dirty-read-vs-phantom/</guid><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;按冲突类型来记：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;冲突&lt;/th&gt;&lt;th&gt;异常&lt;/th&gt;&lt;th&gt;一句话&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;写 → 读&lt;/td&gt;&lt;td&gt;脏读&lt;/td&gt;&lt;td&gt;读到别人&lt;strong&gt;还没提交&lt;/strong&gt;的值，对方一回滚，你读到的就不存在了&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;读 → 写&lt;/td&gt;&lt;td&gt;不可重复读&lt;/td&gt;&lt;td&gt;我读过 A，别人改了 A 并提交，我再读值变了&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;写 → 写&lt;/td&gt;&lt;td&gt;丢失更新&lt;/td&gt;&lt;td&gt;两人都读到 1000、各加 100 写回，最后只有 1100&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;幻读&lt;/strong&gt;不在这张表里：同一个条件（如 &lt;code&gt;WHERE age &gt; 20&lt;/code&gt;）查两次，第二次多出了别人&lt;strong&gt;新插入&lt;/strong&gt;的行。它和范围有关，单行的读写冲突描述不了。InnoDB 在 RR 下靠快照（普通读）和间隙锁（锁定读）防它。&lt;/p&gt;
&lt;p&gt;两个容易混的点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写读冲突不一定是脏读。对方已经提交了你再读，就是正常读到新值。&lt;/li&gt;
&lt;li&gt;脏读在读的那一刻就发生了，和对方后来有没有回滚无关。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>数据库</category></item><item><title>主键、唯一、聚簇、二级、覆盖索引，不是一种分法</title><link>https://exwer-blog.pages.dev/til/mysql-index-taxonomy/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-index-taxonomy/</guid><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;学了一堆索引名词之后，我发现它们根本不在同一层：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;角度&lt;/th&gt;&lt;th&gt;分类&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;叶子上存什么&lt;/td&gt;&lt;td&gt;&lt;strong&gt;聚簇索引&lt;/strong&gt;：叶子放整行，一张表一棵，一般就是主键。&lt;strong&gt;二级索引&lt;/strong&gt;：叶子放索引列 + 主键，要靠主键回表&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;值能不能重复&lt;/td&gt;&lt;td&gt;&lt;strong&gt;主键&lt;/strong&gt;（唯一且非空）、&lt;strong&gt;唯一索引&lt;/strong&gt;、&lt;strong&gt;普通索引&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;由几列组成&lt;/td&gt;&lt;td&gt;单列索引、&lt;strong&gt;联合索引&lt;/strong&gt;（列的顺序就是排序顺序）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;这次查询怎么用&lt;/td&gt;&lt;td&gt;&lt;strong&gt;覆盖索引&lt;/strong&gt;：要的列叶子上都有，不用回表&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;所以「唯一索引」和「二级索引」不冲突：有主键的表里，唯一索引就是一棵二级索引。「覆盖索引」也不是一种要单独建的索引，而是某次查询碰巧用得上的一种用法。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>通配符和正则，* 和 ? 的意思正好不一样</title><link>https://exwer-blog.pages.dev/til/glob-vs-regex/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/glob-vs-regex/</guid><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;通配符（shell 的 glob、SQL &lt;code&gt;LIKE&lt;/code&gt; 的 &lt;code&gt;%&lt;/code&gt; &lt;code&gt;_&lt;/code&gt;）和正则看着像，其实是两套规则：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;通配符&lt;/th&gt;&lt;th&gt;正则&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;任意一串字符&lt;/td&gt;&lt;td&gt;把&lt;strong&gt;前一个&lt;/strong&gt;重复 0 次或多次&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;&lt;td&gt;恰好一个字符&lt;/td&gt;&lt;td&gt;前一个&lt;strong&gt;可有可无&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;匹配范围&lt;/td&gt;&lt;td&gt;整个字符串&lt;/td&gt;&lt;td&gt;默认只要包含就算&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;几个反直觉的例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file?.txt&lt;/code&gt; 匹配 &lt;code&gt;file1.txt&lt;/code&gt;，不匹配 &lt;code&gt;file.txt&lt;/code&gt;；正则 &lt;code&gt;colou?r&lt;/code&gt; 两个都匹配。&lt;/li&gt;
&lt;li&gt;正则 &lt;code&gt;/a*/&lt;/code&gt; 能匹配空串，所以 &lt;code&gt;&quot;xyz&quot;&lt;/code&gt; 也算命中。&lt;/li&gt;
&lt;li&gt;通配符 &lt;code&gt;a*.log&lt;/code&gt; 翻成正则是 &lt;code&gt;^a.*\.log$&lt;/code&gt;：点要转义，两头要锚定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;很多库内部就是把通配符翻译成正则再跑的。&lt;/p&gt;</content:encoded><category>工具</category></item><item><title>数据库里的 lock 和 latch 不是一回事</title><link>https://exwer-blog.pages.dev/til/db-lock-vs-latch/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-lock-vs-latch/</guid><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;操作系统里说的「锁」（mutex、rwlock），在数据库里叫 &lt;strong&gt;latch&lt;/strong&gt;。数据库说的 &lt;strong&gt;lock&lt;/strong&gt; 是更高一层的东西。&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;lock&lt;/th&gt;&lt;th&gt;latch&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;谁持有&lt;/td&gt;&lt;td&gt;事务&lt;/td&gt;&lt;td&gt;线程&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;保护什么&lt;/td&gt;&lt;td&gt;逻辑数据：行、范围、表&lt;/td&gt;&lt;td&gt;内存里的结构：B+ 树的页、哈希表、锁表本身&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;持有多久&lt;/td&gt;&lt;td&gt;到事务提交&lt;/td&gt;&lt;td&gt;临界区，几微秒&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;模式&lt;/td&gt;&lt;td&gt;共享、排他、意向锁……&lt;/td&gt;&lt;td&gt;读、写&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;死锁怎么办&lt;/td&gt;&lt;td&gt;检测出来，回滚一个事务&lt;/td&gt;&lt;td&gt;靠固定的加锁顺序避免，没有回滚&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;InnoDB 的一条 UPDATE 两种都拿：从根往下逐页拿页 latch，到叶子后加行 lock；改完就放页 latch，行 lock 留到 COMMIT。等行锁之前必须先放掉页 latch，否则整页都会被堵住。&lt;/p&gt;
&lt;p&gt;这个区分在别处也适用：Go 里保护 map 的 &lt;code&gt;sync.Mutex&lt;/code&gt; 是 latch；订单「处理中」的行锁、Redis 分布式锁是 lock。通用的一条规则：&lt;strong&gt;拿着短锁时别去等长锁&lt;/strong&gt;，比如持着 Mutex 发 RPC，或者开着事务去调外部 HTTP。&lt;/p&gt;
&lt;p&gt;注意 Java 的 &lt;code&gt;CountDownLatch&lt;/code&gt; 是倒数闸门，和这里的 latch 不是一个意思。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>wait-die 和 wound-wait 怎么记</title><link>https://exwer-blog.pages.dev/til/db-wait-die-wound-wait/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-wait-die-wound-wait/</guid><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;两种都是死锁&lt;strong&gt;预防&lt;/strong&gt;：按事务开始的时间戳分老少，申请锁被挡住时，按规则决定等还是回滚。&lt;/p&gt;
&lt;p&gt;名字的读法是「&lt;strong&gt;老的去申请时怎么做 - 小的去申请时怎么做&lt;/strong&gt;」：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;老的申请、被小的挡住&lt;/th&gt;&lt;th&gt;小的申请、被老的挡住&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;wait-die&lt;/td&gt;&lt;td&gt;等（wait）&lt;/td&gt;&lt;td&gt;自己回滚（die）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;wound-wait&lt;/td&gt;&lt;td&gt;把小的回滚掉（wound）&lt;/td&gt;&lt;td&gt;等（wait）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;两条共同点：&lt;strong&gt;被回滚的永远是年轻的&lt;/strong&gt;；等待只朝一个方向，所以连不成环。被回滚的事务保留原来的时间戳重启，迟早会变成最老的，不会饿死。&lt;/p&gt;
&lt;p&gt;InnoDB 两种都没用，它只做死锁&lt;strong&gt;检测&lt;/strong&gt;（等待图）加超时兜底，发现死锁时挑改动行数少的事务回滚。wound-wait 在分布式数据库里更常见，Google Spanner 用的就是它。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>EXPLAIN 的 type 列：从 const 到 ALL</title><link>https://exwer-blog.pages.dev/til/mysql-explain-type/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-explain-type/</guid><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;type 说的是「用索引怎么定位、最多拿几行」，从好到差：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;type&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;th&gt;例子&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;const&lt;/td&gt;&lt;td&gt;主键或唯一索引等值匹配常量，最多一行&lt;/td&gt;&lt;td&gt;&lt;code&gt;WHERE id = 1&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;eq_ref&lt;/td&gt;&lt;td&gt;连接时被驱动表按主键/唯一索引匹配，每行最多配一行&lt;/td&gt;&lt;td&gt;&lt;code&gt;JOIN m ON m.id = o.member_id&lt;/code&gt; 里的 m&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ref&lt;/td&gt;&lt;td&gt;普通索引等值，可能多行&lt;/td&gt;&lt;td&gt;&lt;code&gt;WHERE name = &apos;zhangsan&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;range&lt;/td&gt;&lt;td&gt;索引上的一段区间&lt;/td&gt;&lt;td&gt;&lt;code&gt;&gt;&lt;/code&gt;、&lt;code&gt;BETWEEN&lt;/code&gt;、&lt;code&gt;IN&lt;/code&gt;、&lt;code&gt;LIKE &apos;x%&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;index&lt;/td&gt;&lt;td&gt;把整棵二级索引从头扫到尾&lt;/td&gt;&lt;td&gt;覆盖索引但用不上最左前缀&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ALL&lt;/td&gt;&lt;td&gt;全表扫描&lt;/td&gt;&lt;td&gt;没索引、对列用了函数、隐式类型转换&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;index 和 ALL 都是全扫，index 只是扫的树更小。判断全表扫描就看 &lt;code&gt;type = ALL&lt;/code&gt;，再配合 key 为 NULL、rows 接近全表行数。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>InnoDB 并发控制 = MVCC + 两阶段锁</title><link>https://exwer-blog.pages.dev/til/mysql-mvcc-plus-2pl/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-mvcc-plus-2pl/</guid><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;课上讲并发控制有几条路线：两阶段锁（悲观，等锁）、时间戳和 OCC（乐观，冲突就回滚）、MVCC（多版本，读写不互堵）。InnoDB 是混着用的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;普通 SELECT&lt;/strong&gt;：走 MVCC，读快照，不加锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UPDATE、DELETE、FOR UPDATE、FOR SHARE&lt;/strong&gt;：走两阶段锁，读最新版本并加锁，锁一直留到 COMMIT 或 ROLLBACK 才放，也就是强严格两阶段锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正因为普通读不加锁，RR 也不是可串行化，挡不住写偏斜。SERIALIZABLE 把普通 SELECT 也变成 FOR SHARE，才全部走两阶段锁。&lt;/p&gt;
&lt;p&gt;几个不守两阶段锁的例外：RC 下 WHERE 不匹配的行判断完就提前放锁；自增锁语句结束就放。&lt;/p&gt;</content:encoded><category>MySQL</category><category>数据库</category></item><item><title>写偏斜：RR 挡不住，SERIALIZABLE 靠死锁挡住</title><link>https://exwer-blog.pages.dev/til/mysql-serializable-write-skew/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-serializable-write-skew/</guid><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;规则是至少一个医生值班，现在 alice 和 bob 都在岗。两人同时请假：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- alice 和 bob 各自执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; SUM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(on_call) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; duty;                     &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 都读到 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; duty &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; on_call &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;自己&apos;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;我实际跑了三种做法：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;做法&lt;/th&gt;&lt;th&gt;结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RR&lt;/td&gt;&lt;td&gt;两人都成功，最后 0 人值班&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SERIALIZABLE&lt;/td&gt;&lt;td&gt;bob 报死锁 1213 被回滚，最后 1 人值班&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RR + &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;bob 在读的时候就等，alice 提交后 bob 读到 1&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;RR 下两人改的是不同的行，行锁不冲突，这就是写偏斜。&lt;/p&gt;
&lt;p&gt;SERIALIZABLE 把普通 SELECT 变成 FOR SHARE：两人都拿到对方那行的共享锁，再各自要排他锁，互相等成死锁，InnoDB 回滚一个。它不是让人排队，而是把写偏斜变成死锁再杀掉一个，所以应用要会重试。&lt;/p&gt;
&lt;p&gt;实际更推荐第三种：在要做判断的读上直接 &lt;code&gt;FOR UPDATE&lt;/code&gt;，第二个人在读的时候就排队，不会死锁。&lt;/p&gt;</content:encoded><category>MySQL</category><category>数据库</category></item><item><title>用依赖图判断一个调度能不能串行化</title><link>https://exwer-blog.pages.dev/til/db-conflict-graph/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-conflict-graph/</guid><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;「结果等价于某个串行顺序」没法直接检查。实际用的是更保守、但能机械判断的&lt;strong&gt;冲突可串行化&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找出所有冲突对：不同事务、同一个数据、至少一个是写。&lt;/li&gt;
&lt;li&gt;每对画一条边，&lt;strong&gt;从先执行的事务指向后执行的&lt;/strong&gt;。只看先后，不看是读还是写。&lt;/li&gt;
&lt;li&gt;图里没有环，就是冲突可串行化，按拓扑顺序就是等价的串行顺序。有环就不是。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;时刻 1  T1 读 A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;时刻 2  T2 读 A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;时刻 3  T1 写 A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;时刻 4  T2 写 A&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;2 → 3：T2 读、T1 写，画 T2 → T1&lt;/li&gt;
&lt;li&gt;3 → 4：两次写，画 T1 → T2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只看 A 就成环了，不可串行化。如果两个事务都是「读余额、加 100、写回」，结果就是 T2 用旧值覆盖了 T1 的修改：丢失更新。&lt;/p&gt;
&lt;p&gt;我第一次画的时候两条边方向都画反了。记住一句：箭头永远从先发生的指向后发生的。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>保证原子性的两种做法：日志和影子分页</title><link>https://exwer-blog.pages.dev/til/db-shadow-paging/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-shadow-paging/</guid><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;事务做到一半崩溃了，改了一半的数据怎么还原？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;日志&lt;/strong&gt;：改之前先记下旧值（undo），回滚或崩溃后据此撤销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;影子分页&lt;/strong&gt;：要改的页先复制一份（写时复制），在副本上改；提交时把页表的根指针切过去。崩溃了就丢掉没切过去的副本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;影子分页听起来更干净，但几乎没人用在通用数据库里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改过的页散落在磁盘各处，变成大量随机写&lt;/li&gt;
&lt;li&gt;提交时要刷所有改过的页和页表&lt;/li&gt;
&lt;li&gt;旧页要回收，也很难支持多个事务并发写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;日志是顺序追加，提交时只要把日志刷盘。&lt;/p&gt;
&lt;p&gt;容易混的一点：崩溃后&lt;strong&gt;撤销&lt;/strong&gt;没提交的修改靠 undo，属于原子性；&lt;strong&gt;重做&lt;/strong&gt;已提交的修改靠 redo，属于持久性。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>比可串行化更严的：外部一致性</title><link>https://exwer-blog.pages.dev/til/db-external-consistency/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-external-consistency/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;可串行化只要求等价于某个串行顺序，&lt;strong&gt;不管现实时间&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;于是可能出现这种情况：你转完账，看到「成功」，打电话让朋友查，朋友查到的还是旧余额。数据库说「我把他的事务排在你前面了」，这也满足可串行化。&lt;/p&gt;
&lt;p&gt;外部一致性（也叫严格可串行化）多了一条要求：T1 在现实中已经提交，之后 T2 才开始，那等价顺序里 T2 必须排在 T1 后面。&lt;/p&gt;
&lt;p&gt;单机上这很容易：一个时钟、一个锁管理器，提交顺序天然就是真实顺序。&lt;/p&gt;
&lt;p&gt;难在分布式：各节点的时钟有偏差，后开始的事务可能拿到更早的时间戳。Google Spanner 的做法是 TrueTime：用 GPS 和原子钟给出「误差不超过 ε」的时间区间，提交前先等过这段误差再宣布成功（commit wait），用一点延迟换来正确。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>Oracle 的 SERIALIZABLE，其实不是可串行化</title><link>https://exwer-blog.pages.dev/til/db-oracle-serializable/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-oracle-serializable/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SQL 标准定义 SERIALIZABLE 的方式是：&lt;strong&gt;不出现脏读、不可重复读、幻读这三种现象&lt;/strong&gt;。它没有要求调度真的可串行化。&lt;/p&gt;
&lt;p&gt;Oracle 的 SERIALIZABLE 实际是&lt;strong&gt;快照隔离&lt;/strong&gt;：每个事务读自己的快照，提交时只检查写写冲突。三种现象确实都不会出现，字面上合格。&lt;/p&gt;
&lt;p&gt;但快照隔离挡不住&lt;strong&gt;写偏斜&lt;/strong&gt;：两个医生都看到「2 人在岗」，各自请假，改的是不同的行，没有写写冲突，结果 0 人在岗。两种串行顺序都不会出现这个结果。&lt;/p&gt;
&lt;p&gt;要挡住它，得追踪每个事务读过什么（读集）。PostgreSQL 9.1 起的 SSI（可串行化快照隔离）做了这件事，开销也更大。&lt;/p&gt;
&lt;p&gt;所以不同数据库里，同一个名字 SERIALIZABLE，保证的东西可能不一样。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>可串行化，不保证结果只有一种</title><link>https://exwer-blog.pages.dev/til/db-serializable-meaning/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-serializable-meaning/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一开始的理解是：串行化就是保证「不管谁先谁后，结果都一样」。这是错的。&lt;/p&gt;
&lt;p&gt;A、B 两个账户各 1000。T1：A 转 100 给 B。T2：两个账户各加 6% 利息。&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;顺序&lt;/th&gt;&lt;th&gt;A&lt;/th&gt;&lt;th&gt;B&lt;/th&gt;&lt;th&gt;总额&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;T1 → T2&lt;/td&gt;&lt;td&gt;954&lt;/td&gt;&lt;td&gt;1166&lt;/td&gt;&lt;td&gt;2120&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T2 → T1&lt;/td&gt;&lt;td&gt;960&lt;/td&gt;&lt;td&gt;1160&lt;/td&gt;&lt;td&gt;2120&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;两种结果不同，但&lt;strong&gt;都对&lt;/strong&gt;。可串行化只要求：并发交错执行的结果，等价于&lt;strong&gt;某一种&lt;/strong&gt;串行顺序。是哪一种，数据库不承诺。&lt;/p&gt;
&lt;p&gt;错的是两种串行顺序都给不出的结果。比如给 A 计息时已经扣了 100、给 B 计息时还没加上那 100，总额就变成 2114。&lt;/p&gt;
&lt;p&gt;为什么只要「某一种」？事务同时到达时本来就没有先后之分，不指定顺序，数据库才有调度的自由。&lt;/p&gt;
&lt;p&gt;这个标准是定义而不是定理：假设每个事务单独跑都能把合法状态变成合法状态，那任何串行顺序都合法，等价于其中一种的交错执行自然也合法。好处是数据库不用懂业务规则就能判断对错。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>别的数据库默认 RC，为什么 MySQL 默认 RR</title><link>https://exwer-blog.pages.dev/til/mysql-why-default-rr/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-why-default-rr/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Oracle、PostgreSQL、SQL Server 默认都是读已提交（RC），只有 MySQL 默认可重复读（RR）。原因是历史包袱。&lt;/p&gt;
&lt;p&gt;早年 binlog 只有 STATEMENT 格式，从库按&lt;strong&gt;提交顺序&lt;/strong&gt;重放 SQL 原文。来看 RC 下会出什么事，表里有 id = 1 和 10：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A: DELETE FROM t WHERE id &amp;#x3C; 10;   -- 不提交&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;B: INSERT INTO t VALUES (5);      -- RC 不锁间隙，插得进去，B 先提交&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A: COMMIT;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;主库：A 删除时 5 还不存在，所以 5 留下了。&lt;/li&gt;
&lt;li&gt;binlog 按提交顺序是「先 INSERT、后 DELETE」，从库重放会把 5 删掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主从不一致了。RR 的间隙锁让 B 必须等 A 提交，执行顺序就和提交顺序一致了。&lt;/p&gt;
&lt;p&gt;如今 binlog 默认是 ROW 格式（记每行改前改后的样子），这个理由已经不成立，所以不少公司改成 RC + ROW。RR 也不是 RC 的升级版：多了防幻读，代价是更多锁冲突和死锁。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>用 INCR 做一个最简单的限流</title><link>https://exwer-blog.pages.dev/til/redis-incr-rate-limit/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/redis-incr-rate-limit/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;INCR&lt;/code&gt; 原子地加 1 并返回新值；key 不存在时按 0 算。比「GET 出来加 1 再 SET 回去」安全：两个客户端不会都读到同一个旧值。&lt;/p&gt;
&lt;p&gt;按分钟计数的限流：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;INCR&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rl:user1:202610150420&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;     # 返回 1，说明是这一分钟第一次&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;EXPIRE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rl:user1:202610150420&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;INCR&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rl:user1:202610150420&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;     # 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;INCR&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rl:user1:202610150420&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;     # 3，超过阈值就拒绝&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;只在返回 1 时设过期时间，后面的 INCR 不会改 TTL。&lt;/p&gt;
&lt;p&gt;几个细节：值不是整数时 INCR 直接报错；类型还是 string；&lt;code&gt;INCRBY&lt;/code&gt;、&lt;code&gt;DECR&lt;/code&gt;、&lt;code&gt;DECRBY&lt;/code&gt; 同理。&lt;/p&gt;
&lt;p&gt;这种写法有个小缺口：INCR 成功、EXPIRE 之前崩了，key 就不会过期。要严格的话，把两步写进一段 Lua。&lt;/p&gt;</content:encoded><category>Redis</category></item><item><title>Redis 里一条普通的 SET，会清掉原来的过期时间</title><link>https://exwer-blog.pages.dev/til/redis-set-clears-ttl/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/redis-set-clears-ttl/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; EX&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;TTL&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;            # -1：过期时间没了，这个 key 永不过期&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;不带 EX 的 SET 是整个覆盖，连 TTL 一起覆盖掉。其他修改值的命令不会动 TTL：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; EX&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;INCR&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;TTL&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;            # 还是 100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;想覆盖值又保留过期时间，用 &lt;code&gt;SET k v KEEPTTL&lt;/code&gt;（6.0 起）。&lt;/p&gt;
&lt;p&gt;缓存刷新时不小心写成普通 SET，key 就再也不会过期了，内存慢慢被占满。&lt;/p&gt;</content:encoded><category>Redis</category></item><item><title>Redis 加锁用 SET NX PX，别用 SETNX + EXPIRE</title><link>https://exwer-blog.pages.dev/til/redis-set-nx-ex/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/redis-set-nx-ex/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; token-A&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; NX&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; PX&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 30000&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;   # OK：拿到锁&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; token-B&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; NX&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; PX&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 30000&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;   # (nil)：已被占，不报错&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;NX&lt;/code&gt; 是不存在才写，&lt;code&gt;PX&lt;/code&gt; / &lt;code&gt;EX&lt;/code&gt; 是过期时间（毫秒 / 秒），一条命令原子完成。&lt;/p&gt;
&lt;p&gt;老写法的问题：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;SETNX&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; token-A&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;   # 返回 1 / 0，不能带过期时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;EXPIRE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; lock:order:42&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;       # 要再补一条&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;两条命令之间进程崩了，这把锁就永远不会过期。&lt;/p&gt;
&lt;p&gt;value 别写死成 1，要写一个能标明「是谁加的」的随机 token，释放时要用到它。&lt;/p&gt;</content:encoded><category>Redis</category></item><item><title>ZREVRANGE 0 2 返回的是 3 个</title><link>https://exwer-blog.pages.dev/til/redis-zrevrange-inclusive/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/redis-zrevrange-inclusive/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;排行榜用有序集合：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;ZADD&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rank:game1&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; alice&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 250&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; bob&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 180&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; carol&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;ZINCRBY&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rank:game1&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 90&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; alice&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;          # 190，不用先读出旧分数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;查前 2 名，我第一反应写成 &lt;code&gt;0 2&lt;/code&gt;：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;ZREVRANGE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rank:game1&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; WITHSCORES&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;  # 返回 3 个！&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;ZREVRANGE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rank:game1&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; WITHSCORES&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;  # bob 250, alice 190&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;start 和 stop &lt;strong&gt;都是闭区间&lt;/strong&gt;，而且从 0 开始。&lt;/p&gt;
&lt;p&gt;查名次也一样：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;ZREVRANK&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rank:game1&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; alice&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;            # 1，表示第 2 名，展示时要 +1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;ZREVRANK&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; rank:game1&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; nobody&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;           # (nil)，不在榜上&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Redis 6.2 起推荐用 &lt;code&gt;ZRANGE key 0 1 REV WITHSCORES&lt;/code&gt; 代替 ZREVRANGE，老版本没有这个写法。&lt;/p&gt;</content:encoded><category>Redis</category></item><item><title>N+1 查询：每条都很快，加起来很慢</title><link>https://exwer-blog.pages.dev/til/db-n-plus-one/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/db-n-plus-one/</guid><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先 1 条查出订单列表，再对每个订单各查 1 次用户，一共 1 + N 条。每条都快，所以慢查询日志里抓不到，但网络往返和解析开销叠起来很可观。&lt;/p&gt;
&lt;p&gt;它不只是 ORM 的问题，根子是&lt;strong&gt;在循环里查询&lt;/strong&gt;：手写 SQL 的 DAO 被循环调用、微服务里逐个调 RPC、GraphQL 的 resolver，都会这样。ORM 只是让它更隐蔽，&lt;code&gt;order.user&lt;/code&gt; 看起来像读一个属性。&lt;/p&gt;
&lt;p&gt;改法是批量取：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; user &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;IN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, ...);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;ORM 一般有三类办法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;显式预加载&lt;/strong&gt;：Django &lt;code&gt;select_related&lt;/code&gt; / &lt;code&gt;prefetch_related&lt;/code&gt;、Rails &lt;code&gt;includes&lt;/code&gt;、SQLAlchemy &lt;code&gt;selectinload&lt;/code&gt;、GORM &lt;code&gt;Preload&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;让懒加载直接报错&lt;/strong&gt;：Rails &lt;code&gt;strict_loading&lt;/code&gt;、Laravel &lt;code&gt;preventLazyLoading&lt;/code&gt;、SQLAlchemy &lt;code&gt;lazy=&quot;raise&quot;&lt;/code&gt;。开发环境打开，N+1 一出现就报错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设计上不提供隐式懒加载&lt;/strong&gt;：比如 Prisma 必须显式写 &lt;code&gt;include&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Redis 同理：循环里一个个 GET，改成 &lt;code&gt;MGET&lt;/code&gt; 或 pipeline。&lt;/p&gt;</content:encoded><category>数据库</category></item><item><title>FOR UPDATE 读到了新值，下一条 SELECT 却又读回旧值</title><link>https://exwer-blog.pages.dev/til/mysql-for-update-snapshot/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-for-update-snapshot/</guid><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;RR 下，B 的快照里 k = 1，之后 C 提交把 k 改成 5：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FOR&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;              &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;FOR UPDATE&lt;/code&gt; / &lt;code&gt;FOR SHARE&lt;/code&gt; 是当前读：读最新已提交的版本并加锁。但它们&lt;strong&gt;不产生新版本&lt;/strong&gt;，快照也不会刷新，所以紧跟着的普通 SELECT 照旧按快照读到 1。&lt;/p&gt;
&lt;p&gt;加锁只是挡住别人再改，不改变可见性。想在事务里一直拿最新值，后续读也要加 &lt;code&gt;FOR UPDATE&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;两种锁定读的区别：&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;别人 FOR SHARE&lt;/th&gt;&lt;th&gt;别人 FOR UPDATE / 改行&lt;/th&gt;&lt;th&gt;别人普通 SELECT&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;我 FOR SHARE&lt;/td&gt;&lt;td&gt;可以&lt;/td&gt;&lt;td&gt;要等&lt;/td&gt;&lt;td&gt;可以&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;我 FOR UPDATE&lt;/td&gt;&lt;td&gt;要等&lt;/td&gt;&lt;td&gt;要等&lt;/td&gt;&lt;td&gt;可以&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;</content:encoded><category>MySQL</category></item><item><title>MVCC 的版本链存在哪</title><link>https://exwer-blog.pages.dev/til/mysql-mvcc-version-chain/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-mvcc-version-chain/</guid><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;每一行在聚簇索引的叶子上只存&lt;strong&gt;最新版本&lt;/strong&gt;，外加两个隐藏列：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DB_TRX_ID&lt;/code&gt;（6 字节）：最后改它的事务 id&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DB_ROLL_PTR&lt;/code&gt;（7 字节）：指向 undo log 里上一个版本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;旧版本不是整行拷贝，而是 undo log 里的记录，只记被改掉的列。读旧版本时，从最新行出发，倒着一条条套 undo，拼出当时的样子。&lt;/p&gt;
&lt;p&gt;所以版本链是一条从新到旧的单向链表：最新的在 B+ 树叶子上，旧的在 undo 表空间里。&lt;/p&gt;
&lt;p&gt;几个推论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;没人再需要的旧版本，由后台 purge 线程清理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长事务会让 undo 越积越多&lt;/strong&gt;：它的快照很旧，之后产生的 undo 都不能删。&lt;/li&gt;
&lt;li&gt;二级索引记录上没有 trx_id，只有页级别的 &lt;code&gt;PAGE_MAX_TRX_ID&lt;/code&gt;，判断不了可见性时就要回表。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>MySQL</category></item><item><title>同一个事务、同一张快照，为什么读到的值变了</title><link>https://exwer-blog.pages.dev/til/mysql-update-is-current-read/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-update-is-current-read/</guid><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;RR 下，事务 B 的快照里 k = 1。之后 C 提交，把 k 改成 2。&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;           &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 1，读快照&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;           &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 3，不是 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;快照没变，变的是这一行多了一个新版本：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;UPDATE 是当前读&lt;/strong&gt;：它读最新已提交的值 2，而不是快照里的 1。否则就会把 C 的修改盖掉（丢失更新）。&lt;/li&gt;
&lt;li&gt;2 + 1 = 3，这个新版本是 B 自己写的。&lt;strong&gt;可见性规则里，自己改的永远可见&lt;/strong&gt;，所以后面的普通 SELECT 读到 3。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;两个推论，都实测过：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只有真被 B 改到的行会这样。没改到的其他行，照样读快照里的旧值。&lt;/li&gt;
&lt;li&gt;UPDATE 如果什么都没改（新值和旧值相同），InnoDB 不写新版本，后面读到的仍是快照。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>MySQL</category></item><item><title>MySQL 表名一律用小写最稳</title><link>https://exwer-blog.pages.dev/til/mysql-table-name-case/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-table-name-case/</guid><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SQL 关键字大小写都能跑，惯例是关键字大写、表名列名小写 snake_case。&lt;/p&gt;
&lt;p&gt;但表名的大小写&lt;strong&gt;和操作系统有关&lt;/strong&gt;，由 &lt;code&gt;lower_case_table_names&lt;/code&gt; 决定：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;系统&lt;/th&gt;&lt;th&gt;默认值&lt;/th&gt;&lt;th&gt;表名区分大小写&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Linux&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;区分&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;macOS&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;不区分&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Windows&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;不区分&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在 Mac 上写 &lt;code&gt;SELECT * FROM User&lt;/code&gt; 能跑，部署到 Linux 上就报找不到 &lt;code&gt;user&lt;/code&gt; 表。这个值只能在初始化数据目录时设置，之后改不了。&lt;/p&gt;
&lt;p&gt;最省事的做法：表名一律小写。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>Redis 说的 binary safe 是什么意思</title><link>https://exwer-blog.pages.dev/til/redis-binary-safe/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/redis-binary-safe/</guid><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Redis 把值当成一串&lt;strong&gt;带长度&lt;/strong&gt;的原始字节，不解释内容，也不认 &lt;code&gt;\0&lt;/code&gt; 结尾。&lt;/p&gt;
&lt;p&gt;C 语言的字符串遇到 &lt;code&gt;\0&lt;/code&gt; 就截断。Redis 自己的字符串结构（SDS）带着长度字段，通信协议也是先发长度再发内容，所以图片、protobuf、任意编码的文本都能原样存取。&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;a\0b&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt; redis-cli&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; -x&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; bin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;STRLEN&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; bin&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;       # 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;你好&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt; redis-cli&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; -x&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; cn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;STRLEN&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; cn&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;        # 6，按 UTF-8 字节数，不是字符数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;代价是它不懂字符集：STRLEN 数的是字节，比较也按字节。类比 MySQL，更像 &lt;code&gt;VARBINARY&lt;/code&gt;，而不是带排序规则的 &lt;code&gt;VARCHAR&lt;/code&gt;。&lt;/p&gt;</content:encoded><category>Redis</category></item><item><title>JSON.SET 的字符串为什么要写两层引号</title><link>https://exwer-blog.pages.dev/til/redis-json-set-quotes/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/redis-json-set-quotes/</guid><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;JSON.SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; product:1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;.name&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;&quot;Striped Bowtie&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;两层引号各管各的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外层单引号&lt;/strong&gt;给 shell / redis-cli：只是让空格不把参数拆开，发到服务器前就被剥掉了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内层双引号&lt;/strong&gt;给 JSON：服务器收到的是 &lt;code&gt;&quot;Striped Bowtie&quot;&lt;/code&gt; 这一串字节。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;能不能省成一层？不能。Redis 的参数只有字节、没有类型，&lt;code&gt;JSON.SET&lt;/code&gt; 规定这个参数是一段 JSON 文本。不加内层引号，服务器分不清 &lt;code&gt;42&lt;/code&gt;、&lt;code&gt;true&lt;/code&gt;、&lt;code&gt;null&lt;/code&gt; 是数字、布尔、空值还是字符串：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;JSON.SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;.n&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;42&apos;&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;      # 数字 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;JSON.SET&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;.s&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;&quot;42&quot;&apos;&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;    # 字符串 &quot;42&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;如果规定「解析失败就当字符串」，写坏的 JSON（比如 &lt;code&gt;{&quot;a&quot;:1&lt;/code&gt;）会被悄悄存成一个字符串，更难排查。&lt;/p&gt;
&lt;p&gt;MySQL 的 &lt;code&gt;JSON_SET&lt;/code&gt; 只要一层引号，是因为 SQL 字面量本身就带类型。&lt;/p&gt;</content:encoded><category>Redis</category></item><item><title>在 M 系列 Mac 上跑 MySQL 5.7</title><link>https://exwer-blog.pages.dev/til/docker-mysql57-apple-silicon/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/docker-mysql57-apple-silicon/</guid><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;想在本地装一个和生产一致的老版本 MySQL，Homebrew 已经没有 5.7 了，8.0 也只给最新的小版本。用 Docker 最省事：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;yaml&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;  mysql57&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;mysql:5.7.44&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    platform&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;linux/amd64&lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;   # 5.7 没有 arm64 镜像，靠模拟运行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&quot;3357:3306&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;      MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;  mysql80&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;mysql:8.0.20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    platform&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;linux/amd64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&quot;3380:3306&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;      MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;root&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;端口错开，用容器自带的客户端连就不会有版本不匹配的问题：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;shell&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; mysql57&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; mysql&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; -uroot&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; -proot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;比版本号更重要的是&lt;strong&gt;配置&lt;/strong&gt;：隔离级别、&lt;code&gt;sql_mode&lt;/code&gt;、&lt;code&gt;binlog_format&lt;/code&gt;、&lt;code&gt;lower_case_table_names&lt;/code&gt; 跟生产对齐，实验结果才可信。&lt;/p&gt;</content:encoded><category>MySQL</category><category>工具</category></item><item><title>MDL 堵表时，用 metadata_locks 找出元凶</title><link>https://exwer-blog.pages.dev/til/mysql-mdl-find-culprit/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-mdl-find-culprit/</guid><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;场景：A 开着事务不提交，B 的 ALTER 在等，C 这些查询排在 B 后面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;只看 &lt;code&gt;SHOW PROCESSLIST&lt;/code&gt; 找不到 A&lt;/strong&gt;：它的 Command 是 &lt;code&gt;Sleep&lt;/code&gt;、Info 是空的，看起来就是个空闲连接。要查 MDL 的持有情况：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; m&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;LOCK_TYPE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;LOCK_STATUS&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;PROCESSLIST_ID&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;PROCESSLIST_COMMAND&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; performance_schema&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;metadata_locks&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; performance_schema&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;threads&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;THREAD_ID&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; m&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;OWNER_THREAD_ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; m&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;OBJECT_SCHEMA&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;lab&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; m&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;OBJECT_NAME&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;cfg&apos;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;我本地复现时的输出（A 是一个开了事务、读完就闲着的连接）：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;LOCK_TYPE          LOCK_STATUS  ID  COMMAND&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SHARED_READ        GRANTED      41  Sleep    ← A：拿着读锁&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SHARED_UPGRADABLE  GRANTED      42  Query    ← B：ALTER 先拿 SU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;EXCLUSIVE          PENDING      42  Query    ← B：在等写锁&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SHARED_READ        PENDING      43  Query    ← C：排在 B 后面&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;认法：同时持有 SU、又在等 EXCLUSIVE 的是 ALTER；GRANTED 的读锁、线程在 Sleep 的是元凶。&lt;/p&gt;
&lt;p&gt;5.7 要先打开 MDL 的记录开关（&lt;code&gt;setup_instruments&lt;/code&gt; 里的 &lt;code&gt;wait/lock/metadata/sql/mdl&lt;/code&gt;），开之前拿的锁记不到；8.0 起默认开。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>MDL 堵表应急：先 KILL 那条 ALTER</title><link>https://exwer-blog.pages.dev/til/mysql-mdl-kill-which/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-mdl-kill-which/</guid><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;找到了元凶 A（不提交的事务）和 B（排在队头的 ALTER），先杀谁？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先杀 B。&lt;/strong&gt; 队头的写锁请求一没，排队的查询立刻拿到读锁（和 A 的读锁兼容），马上止血。我试的时候，KILL B 之后被堵的查询一秒内就返回了。&lt;/p&gt;
&lt;p&gt;杀 A 也能解堵，但会回滚别人的业务事务，而且 B 拿到写锁后还会继续跑一会儿。先止血，再回头查 A 为什么不提交。&lt;/p&gt;
&lt;p&gt;下次改表前做两件事：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 1. 看有没有长事务&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; information_schema&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;innodb_trx&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 2. 在同一个会话里给 ALTER 设个等锁上限&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; SESSION&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; lock_wait_timeout &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; cfg &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; COLUMN remark &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;lock_wait_timeout&lt;/code&gt; 管的是等 MDL，&lt;strong&gt;默认 31536000 秒，也就是一年&lt;/strong&gt;，所以 ALTER 能一直等下去。设成几秒之后，拿不到锁就报 1205 放弃，过会儿重试，不会一直堵着后面的查询。&lt;/p&gt;
&lt;p&gt;别和 &lt;code&gt;innodb_lock_wait_timeout&lt;/code&gt; 搞混：那个管等行锁，默认 50 秒。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>没写 BEGIN 的一条 UPDATE，也是一笔事务</title><link>https://exwer-blog.pages.dev/til/mysql-autocommit-is-transaction/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-autocommit-is-transaction/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;教材讲行锁时总写 &lt;code&gt;BEGIN ... COMMIT&lt;/code&gt;，我一度以为不写就没有事务。&lt;/p&gt;
&lt;p&gt;其实默认 &lt;code&gt;autocommit = 1&lt;/code&gt;，每条语句自己就是一笔事务：执行完自动提交，行锁跟着放掉。教材写 BEGIN，只是为了把事务撑开，让你看得到锁还在。&lt;/p&gt;
&lt;p&gt;反过来也要小心：一旦在事务里，锁就要等到 COMMIT / ROLLBACK 才放，不是语句执行完就放。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>ALTER TABLE 会悄悄提交你的事务</title><link>https://exwer-blog.pages.dev/til/mysql-ddl-implicit-commit/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-ddl-implicit-commit/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; v &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; COLUMN extra &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ROLLBACK&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; v &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 100，UPDATE 没被回滚&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;DDL 执行前会先隐式提交当前事务，执行完自己也会提交。所以 ROLLBACK 时已经没有可回滚的东西了。&lt;/p&gt;
&lt;p&gt;MySQL 的 DDL 不能放在事务里回滚。改表结构前，先把手上的事务处理干净。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>DESCRIBE 只给联合索引的第一列标 Key</title><link>https://exwer-blog.pages.dev/til/mysql-describe-key-column/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-describe-key-column/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;加了联合索引 &lt;code&gt;cb(c, b)&lt;/code&gt;，&lt;code&gt;DESCRIBE geek&lt;/code&gt; 里 b 那一行的 Key 却是空的，我还以为索引没建上：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Field  Type  Null  Key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;a      int   NO    PRI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;b      int   NO    PRI     ← 这是主键，不是 cb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;c      int   YES   MUL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;d      int   YES&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Key 列只有 PRI / UNI / MUL 三种，而且只标在索引的第一列上。看索引要用：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;SHOW &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INDEX&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; geek;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 或 SHOW CREATE TABLE geek;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;别和 EXPLAIN 的 key 列搞混：那一列是这条查询实际选中的索引名。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>Using index condition 不代表真的少回表了</title><link>https://exwer-blog.pages.dev/til/mysql-icp-not-always-helps/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-icp-not-always-helps/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;索引下推（ICP，5.6 起）的意思是：在二级索引的叶子上，先用索引里已有的列判断一遍，不满足的就不回表。&lt;/p&gt;
&lt;p&gt;但 EXPLAIN 里看到 &lt;code&gt;Using index condition&lt;/code&gt;，不等于真省了回表。用 &lt;code&gt;(name, age)&lt;/code&gt; 索引、10 万行数据试了两条：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 1. name 和 age 都用来定扫描起止&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; tuser &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;张1&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; age &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 2. 只有 name 能定起止，age 只能过滤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; tuser &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; LIKE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;张1%&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; age &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;分别开关 &lt;code&gt;optimizer_switch=&apos;index_condition_pushdown=on/off&apos;&lt;/code&gt;，看 InnoDB 交给 Server 的行数：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;查询&lt;/th&gt;&lt;th&gt;ICP 开&lt;/th&gt;&lt;th&gt;ICP 关&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;375&lt;/td&gt;&lt;td&gt;22213&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;第 1 条扫到的每一行本来就满足条件，下推的判断次次为真，一行都没省。第 2 条里 age 定不了范围，下推才真正在叶子上丢掉了 2 万多行。&lt;/p&gt;
&lt;p&gt;另外，不在这棵索引里的列（比如 gender）推不下去，只能回表后再判断。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>INPLACE 改表时一直有人在写，它怎么收尾</title><link>https://exwer-blog.pages.dev/til/mysql-inplace-row-log/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-inplace-row-log/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;INPLACE 重建表的大部分时间里，别人照常读写。这期间的写入先记进 row log。&lt;/p&gt;
&lt;p&gt;我原本担心：写入一直不停，row log 永远补不完，ALTER 岂不是结束不了？&lt;/p&gt;
&lt;p&gt;它不等 row log 变空：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先在不挡写的状态下，把 row log 追到当前末尾。&lt;/li&gt;
&lt;li&gt;拿 MDL 写锁，挡住新的写入。&lt;/li&gt;
&lt;li&gt;剩下的尾巴是有限的，补完就把新表换上去。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果写入速度比补的速度还快，row log 会一直变大，超过 &lt;code&gt;innodb_online_alter_log_max_size&lt;/code&gt; 就报错回滚，ALTER 失败。所以大表改结构，最好挑写入少的时段。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>INSTANT 加列为什么不用改老数据</title><link>https://exwer-blog.pages.dev/til/mysql-instant-how/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-instant-how/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;INSTANT 只改数据字典，原理是给行加了版本号：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;字典里记下每列是第几版加的、默认值是多少。&lt;/li&gt;
&lt;li&gt;每行的行头记着自己是按第几版写的。&lt;/li&gt;
&lt;li&gt;读老行时，按它的版本把后来加的列补上默认值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我一开始以为这是「空间换时间」，其实更准确是&lt;strong&gt;推迟&lt;/strong&gt;：逐行改格式的活被推到了以后。读老行时顺手翻译，更新这一行时顺手按新格式重写，下次重建表时一次做完。加列反而更省空间，因为老行不存默认值。&lt;/p&gt;
&lt;p&gt;版本限制要注意：8.0.12 到 8.0.28 的 INSTANT 只能把列加在表的最后；任意位置加列、INSTANT 删列要 8.0.29 起。生产库版本较老时先确认一下。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>key_len 里的 131 是怎么算出来的</title><link>https://exwer-blog.pages.dev/til/mysql-key-len-131/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-key-len-131/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;utf8mb4&lt;/code&gt; 的 &lt;code&gt;VARCHAR(32)&lt;/code&gt;、允许 NULL，在 EXPLAIN 的 key_len 里是 131：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;32 × 4  = 128   utf8mb4 一个字符最多 4 字节，按列定义的上限算&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      + 2       长度前缀：VARCHAR 有长有短，索引键里记下实际多少字节，比较时才知道在哪结束&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      + 1       NULL 标记：这列允许 NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      = 131&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;允许 NULL 的 INT 没有长度前缀，是 4 + 1 = 5。所以 &lt;code&gt;(name, age)&lt;/code&gt; 两列都用上时 key_len 是 136。&lt;/p&gt;
&lt;p&gt;key_len 表示「定位这段扫描用掉的索引前缀」，只用来过滤的列不算。不想心算，可以直接看列名：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;EXPLAIN FORMAT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=JSON&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; ...;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- &quot;used_key_parts&quot;: [&quot;name&quot;, &quot;age&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;新版本 MySQL 的 JSON 格式默认是第 2 版，没有这个字段，要先 &lt;code&gt;SET explain_json_format_version = 1&lt;/code&gt;。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>一条 ALTER 在等锁，为什么后面所有 SELECT 都卡住了</title><link>https://exwer-blog.pages.dev/til/mysql-mdl-queue/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-mdl-queue/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MDL（元数据锁）是 MySQL 自动加在表上的锁：增删改查拿读锁，改表结构拿写锁。读锁之间能共存，写锁要独占，而且&lt;strong&gt;都要到事务结束才放&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;复现了一下堵表现场：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A：BEGIN; SELECT * FROM cfg;   -- 拿到 MDL 读锁，不提交&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;B：ALTER TABLE cfg ADD COLUMN ...;   -- 要写锁，等 A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C：SELECT COUNT(*) FROM cfg;   -- 普通查询，也在等&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;C 明明和 A 一样只要读锁，为什么也等？因为 MDL 是排队的：&lt;strong&gt;在等的写锁请求优先&lt;/strong&gt;，后来的读锁排在它后面。&lt;/p&gt;
&lt;p&gt;于是 B 等 A，所有新查询等 B，整张表看起来被锁死。线上表现就是：一条 ALTER 卡住，接着连接池被占满，服务报超时。&lt;/p&gt;
&lt;p&gt;MDL 和行锁是两套独立的锁：MDL 在 Server 层锁表结构，行锁在 InnoDB 锁具体的行。增删改查之间只在行锁上冲突，ALTER 和它们只在 MDL 上冲突。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>COPY、INPLACE、INSTANT：ALTER 期间别人还能干什么</title><link>https://exwer-blog.pages.dev/til/mysql-online-ddl-algorithms/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-online-ddl-algorithms/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;算法&lt;/th&gt;&lt;th&gt;怎么做&lt;/th&gt;&lt;th&gt;期间别人能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;COPY&lt;/td&gt;&lt;td&gt;拷一张新表，再换过来&lt;/td&gt;&lt;td&gt;只能读&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;INPLACE&lt;/td&gt;&lt;td&gt;在引擎内部重建，或只建索引&lt;/td&gt;&lt;td&gt;读写&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;INSTANT（8.0.12 起）&lt;/td&gt;&lt;td&gt;只改数据字典，老数据不动&lt;/td&gt;&lt;td&gt;读写，秒级完成&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;可以在语句里指定算法，做不到时 MySQL 会直接报错，而不是悄悄降级：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; iv(v), &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALGORITHM=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;INSTANT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- ERROR: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY/INPLACE.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;MODIFY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; v &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BIGINT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ALGORITHM=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;INPLACE;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- ERROR: Cannot change column type INPLACE. Try ALGORITHM=COPY.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;所以在线上改表时，养成&lt;strong&gt;显式写上 ALGORITHM&lt;/strong&gt; 的习惯：不支持就当场报错，不会意外变成锁表的 COPY。&lt;/p&gt;
&lt;p&gt;大致规律：只改定义的（加列、改默认值）能 INSTANT；要动数据的（建索引、改主键、改可空、重建表）走 INPLACE；改列类型、改字符集至今只能 COPY。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>有索引不用，优化器不是在偷懒</title><link>https://exwer-blog.pages.dev/til/mysql-optimizer-skips-index/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-optimizer-skips-index/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;建了 &lt;code&gt;cb(c, b)&lt;/code&gt;，&lt;code&gt;WHERE c = 1 ORDER BY b&lt;/code&gt; 还是 &lt;code&gt;Using filesort&lt;/code&gt;：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;EXPLAIN &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; geek &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- key: c    Extra: Using filesort&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;EXPLAIN &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; geek &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FORCE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; (cb) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- key: cb   Extra: NULL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;优化器按成本估算：表只有几行、全在一页里，读一页再在内存里排一下，比走 cb 再逐行回表还便宜。数据多了排序变贵，它自然会选 cb。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FORCE INDEX&lt;/code&gt; 很适合用来验证「是不是选错了索引」，但别随手写进业务 SQL，数据分布一变它就可能变成负优化。&lt;/p&gt;
&lt;p&gt;顺带：&lt;code&gt;Using filesort&lt;/code&gt; 只是说没借上索引的顺序、额外排了一次序，数据量小时在内存里排，不一定真用了文件。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>在 InnoDB 上用读未提交，什么也得不到</title><link>https://exwer-blog.pages.dev/til/mysql-read-uncommitted/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-read-uncommitted/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;读未提交（RU）的卖点是「读不被写挡住」。但 InnoDB 的普通 SELECT 走 MVCC 读快照，本来就不会被写挡住。所以在 InnoDB 上选 RU，只额外换来了脏读。&lt;/p&gt;
&lt;p&gt;SQL Server 是个例外：它默认的 RC 是靠锁实现的，读会被写挡住，所以 &lt;code&gt;WITH (NOLOCK)&lt;/code&gt;（等于 RU）在那边很常见。&lt;/p&gt;
&lt;p&gt;同一个隔离级别名字，在不同数据库里的实际代价可能完全不同。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>除了 UPDATE 和 FOR UPDATE，还有谁会拿行锁</title><link>https://exwer-blog.pages.dev/til/mysql-row-lock-statements/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-row-lock-statements/</guid><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DELETE&lt;/strong&gt;：和 UPDATE 一样，扫到的行加排他锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INSERT&lt;/strong&gt;：新行隐式加排他锁，插入前还要拿插入意向锁；撞了唯一键，会在已有的那行上加共享锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INSERT … ON DUPLICATE KEY UPDATE / REPLACE&lt;/strong&gt;：撞键的那行加排他锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INSERT … SELECT、CREATE TABLE … SELECT&lt;/strong&gt;：源表扫到的行加共享锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多表 UPDATE / DELETE&lt;/strong&gt;：只读不改的那张表，也加共享锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外键检查&lt;/strong&gt;：在父表或子表的行上加共享锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SERIALIZABLE 下的普通 SELECT&lt;/strong&gt;：自动变成 &lt;code&gt;FOR SHARE&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最容易忽略的是 &lt;code&gt;INSERT ... SELECT&lt;/code&gt;：一条看起来只是「复制数据」的语句，会把源表扫过的行都锁住。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>BEGIN 之后，快照并没有马上建好</title><link>https://exwer-blog.pages.dev/til/mysql-begin-snapshot-timing/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-begin-snapshot-timing/</guid><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;RR 隔离级别下做个实验：事务 B 开始后、第一次读之前，让 C 把 k 从 1 改成 7 并提交。&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- （这时 C 提交了 k = 7）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 换成这个&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;START TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WITH&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; CONSISTENT &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SNAPSHOT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- （这时 C 提交了 k = 7）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;BEGIN&lt;/code&gt; 只是标记事务开始，执行到第一条普通 SELECT 时才建快照。&lt;code&gt;WITH CONSISTENT SNAPSHOT&lt;/code&gt; 当场就建（只在 RR 下有意义）。&lt;/p&gt;
&lt;p&gt;写业务代码一般不用关心这个，用驱动提供的 &lt;code&gt;BeginTx&lt;/code&gt; 就行。但读教材里的时序例子时，这一点决定了结果。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>先查余额再扣钱，换什么隔离级别都不安全</title><link>https://exwer-blog.pages.dev/til/mysql-business-correctness/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-business-correctness/</guid><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; acct &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;          &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 够 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; acct &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;两步之间，别人可能已经扣过了。调高隔离级别解决不了这个问题。两种正确写法：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 1. 一条带条件的 UPDATE，看影响行数是不是 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; acct &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 2. 先锁住再判断&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; acct &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FOR&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 判断够不够，再 UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;更一般的原则：隔离级别是整个库的配置，不按业务调。业务正确性靠这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;防重复：唯一索引&lt;/li&gt;
&lt;li&gt;防并发改错：带条件的 UPDATE，或者先锁父行&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>MySQL</category></item><item><title>EXPLAIN 的输出变成树了？切回表格用 FORMAT=TRADITIONAL</title><link>https://exwer-blog.pages.dev/til/mysql-explain-formats/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-explain-formats/</guid><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我本机装的新版 MySQL，&lt;code&gt;explain_format&lt;/code&gt; 默认是 TREE，熟悉的表格不见了（8.0 默认仍是表格）：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;text&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&gt; Filter: (tuser.age = 10)  (cost=9846 rows=9774)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -&gt; Table scan on tuser  (cost=9846 rows=97741)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;想看传统表格：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;EXPLAIN FORMAT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;TRADITIONAL &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; ...;            &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 单条&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; SESSION&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; explain_format &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;TRADITIONAL&apos;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;        &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 本连接&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;几种格式怎么选：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TRADITIONAL&lt;/strong&gt;：一表一行，看 type / key / key_len / rows / Extra，教程和文章大多用它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TREE&lt;/strong&gt;：看执行顺序。从缩进最深的那行读起，上一层向它要一行、加工一行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON&lt;/strong&gt;：信息最全，给工具用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EXPLAIN ANALYZE&lt;/strong&gt;：真的执行一遍，给出实际行数和耗时。前三种都不执行，cost 和 rows 只是估算，对不上时用它。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>MySQL</category></item><item><title>联合索引里，范围条件为什么要放最后</title><link>https://exwer-blog.pages.dev/til/mysql-leftmost-prefix-range/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-leftmost-prefix-range/</guid><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;联合索引 &lt;code&gt;(name, age)&lt;/code&gt; 的叶子先按 name 排，name 相同的再按 age 排。所以 &lt;strong&gt;age 只在 name 完全相同的那一小组里有序&lt;/strong&gt;。&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 两列都能定扫描起止：name 固定后，age 是连续的一段&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;张三&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; age &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 只有 name 能定起止：&apos;张%&apos; 本身是范围，这段里 name 各不相同，age 来回跳&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; LIKE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;张%&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; age &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 一列都用不上：age 在整条叶子链上不连续&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; age &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;规律就一句：某一列用了范围，它后面的列只能逐条过滤，没法再缩小扫描范围。所以设计联合索引时，等值列放前面，范围列放最后。&lt;/p&gt;
&lt;p&gt;B+ 树本身可以这样想：上面几层是目录，负责从根跳到起点；最底层的叶子页串成双向链表，从起点顺着往后读。下楼用树，翻范围用链。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>InnoDB 的二级索引里，藏着主键</title><link>https://exwer-blog.pages.dev/til/mysql-secondary-index-has-pk/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-secondary-index-has-pk/</guid><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一直以为二级索引的叶子上只存索引列，其实每条记录后面都跟着主键。InnoDB 正是拿这个主键回表，去主键索引里取整行。&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt; geek&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;  a &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, b &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, d &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; (a, b),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  KEY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; c (c)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 索引 c 的叶子实际存的是 (c, a, b)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;所以 &lt;code&gt;WHERE c = 1 ORDER BY a&lt;/code&gt; 不用再建 &lt;code&gt;(c, a)&lt;/code&gt;：c 相同的记录本来就按 a 排好了。&lt;code&gt;ORDER BY b&lt;/code&gt; 才需要另建 &lt;code&gt;(c, b)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;两个细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主键里已经出现在索引中的列不会存两遍。&lt;/li&gt;
&lt;li&gt;查询要的列在叶子上全都有（包括主键），就不用回表，这就是覆盖索引。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>MySQL</category></item><item><title>binlog 默认记的不是 SQL</title><link>https://exwer-blog.pages.dev/til/mysql-binlog-row/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-binlog-row/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一直以为 binlog 记的是执行过的 SQL。其实 &lt;code&gt;binlog_format&lt;/code&gt; 默认是 &lt;strong&gt;ROW&lt;/strong&gt;：记每一行改前和改后的样子。&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;格式&lt;/th&gt;&lt;th&gt;记什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ROW（默认）&lt;/td&gt;&lt;td&gt;每行改前、改后的值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;STATEMENT&lt;/td&gt;&lt;td&gt;SQL 原文&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MIXED&lt;/td&gt;&lt;td&gt;一般记语句，可能不安全时改记行&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;ROW 的好处是从库重放结果确定，不怕 &lt;code&gt;NOW()&lt;/code&gt;、&lt;code&gt;UUID()&lt;/code&gt;、&lt;code&gt;LIMIT&lt;/code&gt; 没有 ORDER BY 这类「同一条 SQL 两边结果不同」的问题。代价是改很多行时，日志会很大。&lt;/p&gt;
&lt;p&gt;例外：&lt;code&gt;CREATE&lt;/code&gt;、&lt;code&gt;ALTER&lt;/code&gt; 这种 DDL，在 ROW 格式下仍然记语句。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>隔离级别设在哪一层：GLOBAL、SESSION、TRANSACTION</title><link>https://exwer-blog.pages.dev/til/mysql-isolation-scope/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-isolation-scope/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;隔离级别属于一笔事务，不能写在某条 SELECT 上。改它的三种写法，影响范围不同：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; GLOBAL&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; COMMITTED&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 之后新建的连接&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; SESSION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; COMMITTED&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 本连接之后的事务&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;         TRANSACTION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; ISOLATION&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; READ&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; COMMITTED&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 本连接的下一笔事务&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;已经开始的事务改不了。&lt;/p&gt;
&lt;p&gt;不同连接用不同级别也不冲突。隔离级别只管&lt;strong&gt;自己的普通 SELECT 读到哪一版&lt;/strong&gt;，不管锁：改同一行照样排队，和对方是什么级别无关。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>JOIN ... USING(id) 和 ON 有什么不一样</title><link>https://exwer-blog.pages.dev/til/mysql-join-using/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-join-using/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t1 &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t2 &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; (id) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; t1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- 等价于&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t1 &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t2 &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; t1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; t2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; t1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;区别在 &lt;code&gt;SELECT *&lt;/code&gt; 的结果：用 USING 时 id 只出现一列，后面也可以直接写 &lt;code&gt;id&lt;/code&gt;，不用加表名；用 ON 时两张表的 id 各出一列。&lt;/p&gt;
&lt;p&gt;要求是两张表都有这个同名列。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>长事务为什么危险</title><link>https://exwer-blog.pages.dev/til/mysql-long-transaction/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-long-transaction/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一个开着不提交的事务，哪怕只读了一下，也会造成两类麻烦：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;拖住 undo 清理&lt;/strong&gt;：它的快照很旧，在它之后产生的 undo 都不能删，越积越多。5.5 时 undo 放在 ibdata 里，删了也只是文件内部空出来，文件只涨不缩，有人的数据才 20 GB，ibdata 却涨到了 200 GB。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;占着锁&lt;/strong&gt;：MDL 读锁、行锁都要到事务结束才放。想改表、想改这些行的人都得等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;MySQL 没有规定多长算长事务。排查时可以这样看谁开了很久：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; trx_mysql_thread_id, trx_started, trx_query&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; information_schema&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;innodb_trx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; TIME_TO_SEC(TIMEDIFF(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;NOW&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(), trx_started)) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;trx_mysql_thread_id&lt;/code&gt; 就是 &lt;code&gt;KILL&lt;/code&gt; 要用的连接号。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>redo log 的 checkpoint 什么时候往前走</title><link>https://exwer-blog.pages.dev/til/mysql-redo-checkpoint/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-redo-checkpoint/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;redo log 是一圈循环写的文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;write pos&lt;/strong&gt;：当前写到哪，一边写一边往后挪，写到末尾绕回开头。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;checkpoint&lt;/strong&gt;：当前可以擦到哪。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我原本以为事务提交时 checkpoint 就会往前推。其实不是：&lt;strong&gt;它对应的脏页真正写进数据文件之后，checkpoint 才能往前推&lt;/strong&gt;，在那之前这段 redo 还是崩溃恢复要用的。&lt;/p&gt;
&lt;p&gt;平时后台线程慢慢刷脏页、慢慢推 checkpoint。如果更新太猛，write pos 追上了 checkpoint，redo 写满，新的更新就得停下来，先刷脏页把 checkpoint 推走。这时数据库会出现明显的卡顿。&lt;/p&gt;
&lt;p&gt;顺带，redo 里记的是物理修改（哪个表空间、哪一页、页内哪个位置、改成什么字节），里面没有 SQL。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>RR 下的范围 UPDATE，会把还不存在的行也锁住</title><link>https://exwer-blog.pages.dev/til/mysql-rr-gap-lock/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-rr-gap-lock/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;表里只有 id = 1 和 id = 5。事务 A 执行范围更新后不提交，B 来插入 id = 3：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;BETWEEN&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;-- B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;隔离级别&lt;/th&gt;&lt;th&gt;B 的 INSERT&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;REPEATABLE READ&lt;/td&gt;&lt;td&gt;等锁，超时报 1205&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;READ COMMITTED&lt;/td&gt;&lt;td&gt;立刻成功&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;RR 会把扫过的&lt;strong&gt;间隙&lt;/strong&gt;锁到事务结束，挡住往这段范围里插新行（防幻读）。RC 只锁存在的行。&lt;/p&gt;
&lt;p&gt;快照只管普通 SELECT；UPDATE、DELETE、FOR UPDATE 这些会加锁的语句，才是间隙锁的来源。RC 下也不是完全没有间隙锁：外键检查和唯一键冲突时仍会加。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>redo log 和 binlog 怎么保证一起成功</title><link>https://exwer-blog.pages.dev/til/mysql-two-phase-commit/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-two-phase-commit/</guid><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;binlog 是 Server 层写的，redo log 是 InnoDB 写的，两个文件不可能一次写完。一次 COMMIT 内部走的是两阶段提交：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;InnoDB 把 redo 记成 &lt;strong&gt;prepare&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Server 写 binlog&lt;/li&gt;
&lt;li&gt;InnoDB 把 redo 记成 &lt;strong&gt;commit&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;两边靠事务的 XID 对账。崩溃后重启，碰到 prepare 了但没 commit 的事务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;binlog 里有这个 XID：&lt;strong&gt;提交&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;没有：&lt;strong&gt;回滚&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么以 binlog 为准？因为 binlog 可能已经被从库或备份拿走了，主库得和它保持一致。&lt;/p&gt;
&lt;p&gt;这不是你能用 BEGIN 控制的另一笔事务，而是你那次 COMMIT 的实现细节。也没有任何 SQL 能直接往 redo 或 binlog 里写东西。&lt;/p&gt;</content:encoded><category>MySQL</category></item><item><title>Go 没有 map / filter / reduce</title><link>https://exwer-blog.pages.dev/til/go-no-map-filter/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/go-no-map-filter/</guid><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;从 JS 过来，第一反应是找 &lt;code&gt;arr.map(...).filter(...)&lt;/code&gt;。Go 标准库没有这种链式写法，惯用法就是 for range。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;slices&lt;/code&gt; 包里有几个近亲：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;go&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;evens &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;DeleteFunc&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Clone&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(nums), &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; !=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// 反过来的 filter：删掉满足条件的（注意它会改原切片，所以先 Clone）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;flat &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Concat&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;}, []&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;})   &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// 一层 flatten&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;biggest &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Max&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(nums)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Go 1.23 加了迭代器 &lt;code&gt;iter.Seq&lt;/code&gt;，社区也讨论过官方的 Map / Filter / Reduce，但目前没有进标准库。&lt;/p&gt;</content:encoded><category>Go</category></item><item><title>fs.WalkDir 的第一个参数，不一定是 os.DirFS</title><link>https://exwer-blog.pages.dev/til/go-fs-walkdir/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/go-fs-walkdir/</guid><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;go&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;fs.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;WalkDir&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(os.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;DirFS&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(root), &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt; fs&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;DirEntry&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;	// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;第一个参数的类型是 &lt;code&gt;fs.FS&lt;/code&gt;，一个抽象的文件系统。&lt;code&gt;os.DirFS&lt;/code&gt; 只是把磁盘目录包装成 FS，换成别的也行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;embed.FS&lt;/code&gt;：编译进二进制的文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fstest.MapFS&lt;/code&gt;：测试用的内存文件系统&lt;/li&gt;
&lt;li&gt;zip 包等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二个参数才是这个 FS 里的起点，常写 &lt;code&gt;&quot;.&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;回调里的 &lt;code&gt;fs.DirEntry&lt;/code&gt; 是当前条目的轻量信息：&lt;code&gt;Name()&lt;/code&gt;、&lt;code&gt;IsDir()&lt;/code&gt;、&lt;code&gt;Type()&lt;/code&gt; 都不用再 Stat。要大小、修改时间时才调 &lt;code&gt;d.Info()&lt;/code&gt;。注意 &lt;code&gt;err != nil&lt;/code&gt; 时 d 可能是 nil。&lt;/p&gt;
&lt;p&gt;写测试时用 MapFS，不用真的在磁盘上建目录：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;go&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;mfs &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt; fstest&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;MapFS&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;	&quot;logs/app.log&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;:     {Data: []&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;)},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;	&quot;logs/nginx/a.log&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;: {Data: []&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;)},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content:encoded><category>Go</category></item><item><title>Go 排序：slices.SortFunc 返回的不是 bool</title><link>https://exwer-blog.pages.dev/til/go-slices-sortfunc/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/go-slices-sortfunc/</guid><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;包名是 &lt;code&gt;slices&lt;/code&gt;（复数）。比较函数返回负数、0、正数，用 &lt;code&gt;cmp.Compare&lt;/code&gt; 最省事：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;go&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;SortFunc&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(ps, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt; Person&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; cmp.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Compare&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(a.Age, b.Age)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// 多个字段：先按年龄，再按名字（cmp.Or 取第一个非 0 的结果）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;SortFunc&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(ps, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt; Person&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; cmp.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Or&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(cmp.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Compare&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(a.Age, b.Age), cmp.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Compare&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(a.Name, b.Name))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;排序是&lt;strong&gt;原地&lt;/strong&gt;的，没有返回值。想保留原顺序，先克隆：&lt;/p&gt;
&lt;figure class=&quot;code-block&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;&lt;svg class=&quot;icon-code&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;m18 16 4-4-4-4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m6 8-4 4 4 4&quot;&gt;&lt;/path&gt;&lt;path d=&quot;m14.5 4-5 16&quot;&gt;&lt;/path&gt;&lt;/svg&gt;go&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-copy&quot; aria-label=&quot;复制代码&quot;&gt;&lt;svg class=&quot;icon-copy&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;rect width=&quot;14&quot; height=&quot;14&quot; x=&quot;8&quot; y=&quot;8&quot; rx=&quot;2&quot; ry=&quot;2&quot;&gt;&lt;/rect&gt;&lt;path d=&quot;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;icon-check&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M20 6 9 17l-5-5&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class=&quot;code-copy__label&quot;&gt;复制&lt;/span&gt;&lt;/button&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code github-dark-default&quot; style=&quot;background-color:#0d1117;color:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt; slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Clone&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(s)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;slices.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;Sort&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3&quot;&gt;(c)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;b := a&lt;/code&gt; 不行：它只复制了切片头（指针、长度、容量），底层数组还是同一个，排 b 就等于排 a。&lt;/p&gt;
&lt;p&gt;数组要先 &lt;code&gt;arr[:]&lt;/code&gt; 变成切片再排。&lt;/p&gt;</content:encoded><category>Go</category></item><item><title>「超过三张表禁止 JOIN」，真正的原因</title><link>https://exwer-blog.pages.dev/til/mysql-join-limit-reason/</link><guid isPermaLink="true">https://exwer-blog.pages.dev/til/mysql-join-limit-reason/</guid><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一些开发规范里有这条，但 JOIN 本身没有错。原因主要两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;旧版本优化器弱&lt;/strong&gt;：8.0.18 之前没有 hash join，连接列没索引时只能嵌套循环，行数一多就是 M × N 次比较。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分库分表&lt;/strong&gt;：两张表不在同一个库里，数据库层面就 JOIN 不了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以在线接口常见的做法是单表查，再在应用层拼起来。&lt;/p&gt;
&lt;p&gt;PostgreSQL 圈子里很少有这种规定：它很早就有 Nested Loop、Hash、Merge 三种连接，也很少拆分片。这条规范更像是「MySQL 5.x 加分库分表」那个时代的产物，不是 SQL 的真理。&lt;/p&gt;
&lt;p&gt;不过有一点到今天也成立：点查和 WHERE 过滤仍然要靠索引，hash join 救不了全表扫描。&lt;/p&gt;</content:encoded><category>MySQL</category></item></channel></rss>