ARTS 0809: 反转链表、Shopify 如何用 MySQL 解决超卖与 AI 时代程序员的价值

每周完成一个 ARTS: 至少做一个 leetcode 的算法题、阅读并点评至少一篇英文技术文章、学习至少一个技术技巧、分享一篇有观点和思考的技术文章。(也就是 Algorithm、Review、Tips、Share 简称 ARTS)

Algorithm

这周是简单难度,反转链表 https://leetcode.cn/problems/reverse-linked-list/?envType=study-plan-v2&envId=selected-coding-interview

我也记录一下自己的两个错误的思路:

需要反转的链表:1 -> 2 -> 3 -> 4 -> 5

1)如果两两互换的话

1 -> 2 -> 3 -> 4 -> 5
把 2 插到最前 → 2 -> 1 -> 3 -> 4 -> 5
把 3 插到最前 → 3 -> 2 -> 1 -> 4 -> 5
把 4 插到最前 → 4 -> 3 -> 2 -> 1 -> 5
把 5 插到最前 → 5 -> 4 -> 3 -> 2 -> 1

这个思路和正确的思路其实是有点类似了,但是她需要把最后的结果和来源写到一起,非常乱

2)如果是首尾互换的逻辑呢,比如下面的例子:

初始
1 -> 2 -> 3 -> 4 -> 5
↑                   ↑
左                   右

第 1 步:换 1 和 5
5 -> 2 -> 3 -> 4 -> 1
     ↑         ↑
    左         右

第 2 步:换 2 和 4
5 -> 4 -> 3 -> 2 -> 1
          ↑
       左右相遇(或交叉)

完成
5 -> 4 -> 3 -> 2 -> 1

如果这样写的话,那么需要前后两个不同的指针,而且这个 next 的逻辑比较麻烦

「正确」的思路:

1 -> 2 -> 3 把你这段代码逐步画开:

1、初始

pre = null
cur = 1

null      1 -> 2 -> 3
 ↑        ↑
pre      cur

2、第 1 轮

① next = cur.next
null      1 -> 2 -> 3
 ↑        ↑    ↑
pre      cur  next

② cur.next = pre
null <- 1      2 -> 3
 ↑      ↑      ↑
pre    cur    next

③ pre = cur; cur = next
null <- 1      2 -> 3
        ↑      ↑
       pre    cur

3、第 2 轮

① next = cur.next
null <- 1      2 -> 3
        ↑      ↑    ↑
       pre    cur  next

② cur.next = pre
null <- 1 <- 2      3
        ↑    ↑      ↑
       pre  cur    next

③ pre = cur; cur = next
null <- 1 <- 2      3
             ↑      ↑
            pre    cur

4、第 3 轮

① next = cur.next
null <- 1 <- 2      3 -> null
             ↑      ↑      ↑
            pre    cur    next

② cur.next = pre
null <- 1 <- 2 <- 3      null
             ↑    ↑       ↑
            pre  cur     next

③ pre = cur; cur = next
null <- 1 <- 2 <- 3      null
                  ↑       ↑
                 pre     cur

此时 cur == null,循环结束,return pre → 新头是 3

3 -> 2 -> 1
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

}

Review

文章是这一篇,关于 Shopify 单纯使用 MySQL 解决超卖的问题,原文链接: https://shopify.engineering/scaling-inventory-reservations

防止超卖一般需要两个操作:

  • Reserve(预留):支付开始时,把商品标记为已预留(短时占用,比如几分钟)。
  • Claim(核销):支付成功后,从库存 MySQL 扣减数量。

预留商品不能太久,要不然影响销量;支付成功核销不能太迟,否则客服有的忙了

Shopify 系统之前的做法是:

1、把预留操作再 Redis 里面进行,每一个商品一个 key ,如果触发预留操作就是用 DECR,核销操作就是 INCR。

2、核销完成之后就需要更新 MySQL

问题:

看起来很完美,Redis 能扛住并发,MySQL 也不会被直接打爆。但是真的这么完美吗?其实我们想一下,维护一套 Redis 集群当然这不是最大的问题,最大的问题就是不能把这两步合成一个原子操作,可能会出现超卖或者少卖问题。那么有没有更好的解决方案?

有没有其他的方案,有的稳重中提到了使用 MySQL8.0 的 SKIP LOCKED (下面有这个的介绍)新特性进行解决,怎么解决?

很巧妙,他把库存不单独存储再一个行里面,而是有多少库存插入多少行记录,在加持上这个 SKIP LOCKED 特性,不仅保持的 ACID 而且还减少 Redis 维护成本,非常舒服~

虽然之前没有办法解决的问题,但是现在由于 MySQL 的更新,解决起来也更简单,时代真的是会变化的。


面试鸭上面也有关于 SKIP LOCKED 的介绍 https://www.mianshiya.com/question/1780933295505174530#heading-5

image-20260809204107781

Tips

1、Github Action 打包 docker image 真的好用,现在 AI 时代让 AI 写一个 yml ,之后用 Github Desktop push 到远程上面也非常方便。我本地 build 需要花了很久才还没好,我直接暂停。

2、Cursor 会读取 Claude 的 skills 会占用很多宝贵的 context ,建议检查一下

3、之前看社区说 Opus5 效果不太好,有人说可以用 Opus4.8 plan + Opus5 执行

Share

大佬的观点 https://blog.senko.net/code-was-never-the-hard-part-is-an-insult-to-all-programmers

作者反驳「写代码很容易,难的是想清楚做什么」:写代码本身就是硬手艺——高薪、面试、经典著作、天才程序员和遍地 bug 都说明这一点;反过来,「定需求更难」也经不起推敲,否则 PM、调研、客成早该是公司明星。真正重要的是两者都要:既懂系统怎么建,也懂为什么建。面对 AI 变革别靠自我安慰,要好奇又批判地适应;变的是工具与角色,不变的是复杂度、维护、用户说不清需求。别把理解、判断和品味外包给 AI。

我最近还看到开发 FFmpeg 和 VLC 的 Podcast(虽然没看完😂) ,大佬是真的牛

posted @ 2026-08-09 20:44  leikooo  阅读(6)  评论(0)    收藏  举报