使用 AI 调试 Databricks 上的 PostgreSQL 数据库:完整指南

本文有一个短视频:TikTok

1. 概述

当应用程序连接到 Databricks 上的 PostgreSQL(通过 Lakebase 或 Lakehouse Federation)时遇到错误,手动阅读 stack trace、HikariCP 日志和 EXPLAIN ANALYZE 会花费大量时间。Databricks 集成的 AI Assistant 可以读取日志、SQL 查询和连接池配置,直接建议原因和修复方案。

这篇文章解释了 AI 数据库调试机制如何工作,然后介绍三种常见的 PostgreSQL 错误场景(连接池耗尽、N+1 查询、权限不足),并附带 Spring Boot 代码,让你能够重现这些错误并验证 AI 的建议。读完后,你会知道如何准备日志和查询,使 AI(或你自己)能更快更准确地诊断问题。

2. Databricks Postgres(Lakebase)是什么

Lakebase 是在 Databricks workspace 内运行的托管 PostgreSQL,用于 OLTP(事务性)工作负载,与用于 OLAP 的 Delta Lake 并行。后端应用程序连接到 Lakebase 就像连接普通 PostgreSQL 一样——通过 JDBC,使用 Databricks 提供的 host/port/database。

2.1. 从 Spring Boot 连接

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
spring.datasource.url=jdbc:postgresql://<workspace>.lakebase.databricks.com:5432/orders_db
spring.datasource.username=app_user
spring.datasource.password=${DB_PASSWORD}
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.connection-timeout=3000

这是最小配置:maximum-pool-size 和 connection-timeout 是两个参数,会在第 4 部分的调试案例中反复出现,因为 AI 通常从这里开始分析。

3. Databricks 中的 AI 数据库调试如何工作

3.1. AI 读取的数据源

Databricks Assistant 不是“随机猜测”——它从三个数据源读取:

  • Stack trace / 异常日志来自 job 或 notebook。
  • 查询计划(EXPLAIN ANALYZE)当你询问关于慢查询的问题时。
  • Schema 和元数据(表、索引、约束)来验证 SQL 的有效性。

换句话说,AI 的好坏完全取决于你的日志详细程度。只打印 "Connection is not available" 的异常会比带有当前池大小和活跃连接数的日志提供更模糊的建议。

3.2. 典型调试流程

1. 你将 stack trace 或错误 SQL 粘贴到 Databricks Assistant。
2. AI 识别错误类型:连接池、语法、权限、锁定、超时...
3. AI 将其与 workspace 中现有的 schema/元数据进行交叉检查。
4. AI 建议修复:更改配置、修复 SQL、添加索引、运行 GRANT...
5. 你应用修复,重新运行,AI 确认日志是否清晰。

下一部分用三个具体案例模拟这个流程。

4. 实践:重现错误和阅读调试建议

4.1. 准备数据

CREATE TABLE customers (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_id BIGINT REFERENCES customers(id),
    total NUMERIC(12,2) NOT NULL,
    created_at TIMESTAMP DEFAULT now()
);

对应的 entity:

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;

    private BigDecimal total;
}

4.2. 案例 1——连接池耗尽

这个 Service 存在连接泄漏错误,因为它手动打开 EntityManager 而不关闭:

@Service
public class OrderReportService {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Order> findAllOrdersUnsafe() {
        EntityManager em = entityManager.getEntityManagerFactory()
                .createEntityManager(); // 永远不会 close()
        return em.createQuery("SELECT o FROM Order o", Order.class)
                .getResultList();
    }
}

连续调用此 API 会生成日志:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available,
request timed out after 3000ms.

当你将此 stack trace 和 application.properties 文件提供给 AI 时,返回的建议通常是:

- 池大小(10)不是主要原因;问题在于连接没有被归还到池中。
- 检查所有手动调用 createEntityManager()/getConnection() 的地方。
- 确保使用 try-with-resources 或让 Spring 管理事务。

正确的修复是完全放弃自己创建 EntityManager,使用标准 repository:

public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findAll();
}
@Service
public class OrderReportService {

    private final OrderRepository orderRepository;

    public OrderReportService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public List<Order> findAllOrdersSafe() {
        return orderRepository.findAll(); // Spring 自动管理连接
    }
}

注意: 遇到此错误时增加 maximum-pool-size 是错误的方向——它只会延迟问题而不是修复实际的连接泄漏。

4.3. 案例 2——N+1 查询使系统变慢

public List<OrderSummary> summarize(List<Order> orders) {
    return orders.stream()
            .map(o -> new OrderSummary(o.getId(), o.getCustomer().getName())) // 懒加载触发
            .toList();
}

因为 customer 是 FetchType.LAZY,每个元素都会产生一个单独的 customers 查询。在 Databricks 日志上运行 EXPLAIN ANALYZE 会看到数百次:

SELECT * FROM customers WHERE id = $1;

AI 在读取这个查询计划时通常会指出:

- 在一个请求内重复 200 次相同的 SELECT ... WHERE id = $1 模式。
- 怀疑是懒加载导致的 N+1 问题,没有 JOIN FETCH。
- 建议:使用 JOIN FETCH 或 @EntityGraph 将其合并为一个查询。

通过 JPQL JOIN FETCH 修复:

@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();

结果:只有一条 SELECT ... JOIN customers ... 查询,而不是 N+1 个单独的查询。

4.4. 案例 3——权限不足

SELECT * FROM orders WHERE total > 1000;
ERROR: permission denied for table orders

AI 查阅 Databricks 中的角色元数据并返回适当的 GRANT 建议:

GRANT SELECT ON orders TO app_user;

注意: 在生产环境中执行 GRANT 建议前务必检查——AI 不知道你的内部安全政策,只知道使查询工作所需的语法。不要授予超过最低需要权限的权限(最小权限原则)。

5. 编写测试来确认修复

修复案例 4.3 中的 N+1 后,需要测试以确保不会回归:

@DataJpaTest
class OrderRepositoryTest {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void shouldFetchOrdersWithCustomerInOneQuery() {
        Customer customer = new Customer();
        customer.setName("Nón Lá Dev");
        entityManager.persist(customer);

        Order order = new Order();
        order.setCustomer(customer);
        order.setTotal(new BigDecimal("150.00"));
        entityManager.persist(order);
        entityManager.flush();

        List<Order> orders = orderRepository.findAllWithCustomer();

        assertThat(orders)
                .hasSize(1)
                .first()
                .extracting(o -> o.getCustomer().getName())
                .isEqualTo("Nón Lá Dev");
    }
}

此测试不直接“计数查询”,但结合 Hibernate statistics(spring.jpa.properties.hibernate.generate_statistics=true),你可以记录执行的查询数量并 assert 它等于 1——这正是 AI 用来确认修复是否有效的日志类型。

6. 监控使 AI 调试更有效

为了让 AI(或后来阅读日志的同事)快速诊断,请记录结构化日志而不是仅仅 e.printStackTrace():

@RestControllerAdvice
public class DatabaseExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(DatabaseExceptionHandler.class);

    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<String> handle(DataAccessException ex) {
        log.error("db_error_type={}, sql_state={}, message={}",
                ex.getClass().getSimpleName(),
                extractSqlState(ex),
                ex.getMostSpecificCause().getMessage());
        return ResponseEntity.status(503).body("数据库错误,请稍后重试");
    }

    private String extractSqlState(DataAccessException ex) {
        if (ex.getCause() instanceof SQLException sqlEx) {
            return sqlEx.getSQLState();
        }
        return "unknown";
    }
}

键值对格式的日志(db_error_type=、sql_state=)比纯文本 stack trace 更容易解析,是你粘贴到 Databricks Assistant 时询问“这个错误是由什么原因引起的”的理想输入。

7. 何时不应使用 AI 调试

  • 与敏感数据相关的错误:不要将包含真实客户数据的日志粘贴到 AI assistant,即使是内部的——请先进行脱敏。
  • 基础设施级性能问题(磁盘 I/O、区域间网络延迟):AI 可以读取查询计划但看不到物理基础设施层,容易给出错误的建议(例如建议“添加索引”而实际原因是网络)。
  • 一次性瞬时错误:如果异常只因网络抖动发生过一次,用 AI 调试会浪费时间,最好只是重试并继续监控。
  • 代替代码审查:AI 关于 GRANT 或更改池大小的建议需要具有基础设施权限的人确认,不应自动应用到生产环境。

8. 结论

Databricks 上的 AI 数据库调试在快速读取 stack trace、查询计划和元数据以确定根本原因方面很强大——连接泄漏、N+1 查询、权限不足是三个非常常见的案例,本文通过 Spring Boot 和 PostgreSQL 进行了重现。真正的价值在于编写结构化日志和测试来确认修复,因为 AI 的效果完全取决于你提供的数据质量。在将建议应用到真实系统之前,始终要验证建议——特别是 GRANT 权限或基础设施更改。

databrickspostgresqlaidebuggingspring-boot