期末周复习到崩溃:数据库事务、栈专题,还有没看懂的 RAG 项目

前言

大家好,我是瓦瓦gogo,这是这两天的学习复盘。

这两天明显进入期末周节奏了,整个人的时间被学校考试切得很碎。好消息是学校书本上的数据库内容终于复习完了;坏消息是数值计算刚开始复习,就已经感觉有点痛苦了。公式、误差、迭代这些东西看起来都不算特别长,但真要自己算、自己理解,还是挺折磨的。

原本还想着这几天抽时间看看之前项目,尤其是简历上写过的 RAG 项目和之前公司的业务代码,结果现实是:期末一来,项目基本没时间碰。现在只能先保证学校考试不掉链子,再尽量每天挤一点时间补面试八股和算法。

数据库书本内容复习完了

这两天学校数据库这块主要是在按书本复习。相比面试八股,书本内容更偏系统一些,比如关系模型、SQL、关系代数、数据库设计、范式、事务这些。以前学的时候感觉知识点很多很散,这次复习完之后,至少对数据库这门课的整体结构清楚了一点。

我现在的感觉是,学校考试和面试复习关注点不太一样。考试更容易考概念、计算、SQL 写法和规范化分析;面试更喜欢问实际开发中会遇到的问题,比如事务为什么会出问题、隔离级别怎么选、索引为什么失效、慢 SQL 怎么排查。

所以复习书本的时候,我也顺便把数据库事务和隔离机制当成八股重新过了一遍。

数据库事务和隔离机制

事务这块最基础的是 ACID:

  • 原子性:事务里的操作要么全部成功,要么全部失败
  • 一致性:事务执行前后,数据库要保持合法状态
  • 隔离性:多个事务并发执行时,彼此之间不能随便干扰
  • 持久性:事务提交之后,结果要持久保存

面试里一般不会只问 ACID 是什么,更容易继续问并发事务会产生哪些问题。这里主要有三个:

  • 脏读:一个事务读到了另一个事务还没提交的数据
  • 不可重复读:同一个事务里两次读取同一行数据,结果不一样
  • 幻读:同一个事务里两次按条件查询,结果集数量变了

然后就是四种隔离级别:

  • 读未提交:可能出现脏读、不可重复读、幻读
  • 读已提交:解决脏读,但可能出现不可重复读和幻读
  • 可重复读:解决脏读和不可重复读,MySQL InnoDB 默认是这个级别
  • 串行化:隔离最强,但并发性能最差

我觉得这里最容易混的是不可重复读和幻读。不可重复读更偏向同一行数据被修改了,比如第一次查余额是 100,第二次变成 80。幻读更偏向结果集变化,比如第一次查符合条件的记录有 3 条,第二次变成 4 条。

这块后面还得继续结合 MVCC、undo log、read view 去理解,不然只背隔离级别还是有点虚。

算法:开始做栈专题

算法这两天开始做栈专题,刷了三道比较基础但很经典的题:用栈模拟队列、用队列模拟栈、判断括号是否合法。

这几题看起来简单,但我感觉它们适合用来重新理解数据结构的特性:栈是后进先出,队列是先进先出。题目本质上就是让我们用一种结构模拟另一种结构。

用栈模拟队列

这题的思路是准备两个栈:一个输入栈,一个输出栈。入队时直接放到输入栈;出队时如果输出栈为空,就把输入栈里的元素全部倒过去。

class MyQueue {
    private Deque<Integer> in = new ArrayDeque<>();
    private Deque<Integer> out = new ArrayDeque<>();

    public void push(int x) {
        in.push(x);
    }

    public int pop() {
        move();
        return out.pop();
    }

    public int peek() {
        move();
        return out.peek();
    }

    public boolean empty() {
        return in.isEmpty() && out.isEmpty();
    }

    private void move() {
        if (out.isEmpty()) {
            while (!in.isEmpty()) {
                out.push(in.pop());
            }
        }
    }
}

关键点是只有 out 为空时才倒数据,不是每次 pop 都倒。这样可以避免重复搬运元素,整体效率更好。

用队列模拟栈

用队列模拟栈可以用一个队列完成。每次新元素入队之后,把前面的旧元素依次移到队尾,这样队头永远是最后入栈的元素。

class MyStack {
    private Queue<Integer> queue = new LinkedList<>();

    public void push(int x) {
        queue.offer(x);
        int size = queue.size();
        for (int i = 0; i < size - 1; i++) {
            queue.offer(queue.poll());
        }
    }

    public int pop() {
        return queue.poll();
    }

    public int top() {
        return queue.peek();
    }

    public boolean empty() {
        return queue.isEmpty();
    }
}

这题让我理解到,模拟数据结构不是死记写法,而是要想办法维护目标结构的访问顺序。栈要后进先出,所以每次 push 后都把新元素转到队头。

判断括号是否合法

括号匹配是栈最经典的应用。遇到左括号就入栈,遇到右括号就看栈顶是不是对应的左括号。

public boolean isValid(String s) {
    Deque<Character> stack = new ArrayDeque<>();

    for (char c : s.toCharArray()) {
        if (c == '(') {
            stack.push(')');
        } else if (c == '[') {
            stack.push(']');
        } else if (c == '{') {
            stack.push('}');
        } else if (stack.isEmpty() || stack.pop() != c) {
            return false;
        }
    }

    return stack.isEmpty();
}

这里我觉得比较巧的写法是:遇到左括号时,不是把左括号本身入栈,而是把它期待匹配的右括号入栈。这样遇到右括号时,只需要判断当前字符和栈顶是否相等,代码会更简洁。

RAG 项目到底应该怎么学

这两天还有一个很现实的问题:简历上以前写过 RAG 项目,但现在回头看,其实很多地方没有真正弄懂。

当时更多是跟着教程跑起来,知道大概是文档切分、向量化、存到向量库、检索、再把结果交给大模型回答。但如果面试官继续问,比如为什么要切 chunk、chunk 多大合适、召回结果怎么排序、为什么会答非所问、怎么评估效果,我可能就不太能讲清楚。

所以现在有点想求教一下:学项目到底应该怎么学?尤其是 RAG 这种项目,是不是不能只停留在“我把项目跑起来了”,而是应该按链路拆开:

  • 数据怎么进来
  • 文档怎么切分
  • embedding 怎么生成
  • 向量库怎么存和查
  • 检索结果怎么过滤和排序
  • prompt 怎么拼接
  • 回答效果怎么评估
  • 出问题时怎么定位

我现在打算等 7 月 2 号期末考完之后,7 月中继续投简历,看看能不能找一份大一点公司的日常实习。在这之前,项目这块必须重新补,不然简历上写了但讲不清,面试反而会变成扣分项。

今日总结

这两天整体就是期末周夹着面试准备一起往前走。数据库书本内容复习完了,顺便过了一遍事务和隔离级别;算法开始做栈专题,三道题都比较基础,但能帮我重新理解栈和队列的特性;数值计算刚开始复习,确实有点难。

现在比较焦虑的是项目没时间看,尤其是 RAG 项目之前没有真正吃透。后面如果还有时间,我想把项目按“链路、核心问题、可优化点、面试怎么讲”重新整理一遍。期末周先活下来,项目和简历等考试结束后再集中补。

Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐