SQL vs SQL Builder vs ORM

与关系数据库对话:SQL vs SQL Builder vs ORM

前言

开发网站无可避免的会和关系数据库打交道,而互动的方式有三种:

  • SQL
  • SQL Builder
  • Object-Relational Mapping (ORM)

这三者有着不同的使用方式、优缺点与适用场景,了解它们的差异能帮助我们做出更适合项目需求的技术选择。

SQL

SQL 是与关系数据库沟通的语言,虽然不同数据库常见语法上有些许差异,但彼此间差异不大。想要获得最好的灵活性与性能可以使用 SQL,极大的灵活性也可能暗藏 SQL Injection 风险。

// ❌ 用户可能添加恶意输入造成拼凑出恶意的 SQL 指令
const username = req.body.username;
const password = req.body.password;
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query, (err, results) => {
if (results.length > 0) {
// 登录成功
} else {
// 登录失败
}
});
// ✅ 通过 Prepared Statement 安全处理用户输入防止 SQL injection
const username = req.body.username;
const password = req.body.password;
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password], (err, results) => {
// 安全地处理查询
});

SQL Builder

介于灵活性与安全性之间的方案,通过抽象函数组合,以代码的方式构建 SQL,从而规避 SQL Injection 问题。

// Drizzle ORM
import { eq, and } from 'drizzle-orm';
import { db } from './db';
import { users } from './schema';
const username = req.body.username;
const password = req.body.password;
const result = await db
.select()
.from(users)
.where(and(
eq(users.username, username),
eq(users.password, password)
));
if (result.length > 0) {
// 登录成功
} else {
// 登录失败
}

ORM

提供一种将数据表映射成程序中的 Class、数据列对应成对象 Object 的方式。通常 ORM 也会处理不同 DB 之间的差异、SQL Injection 问题。

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const username = req.body.username;
const password = req.body.password;
const user = await prisma.user.findFirst({
where: {
username,
password,
},
});
if (user) {
// 登录成功
} else {
// 登录失败
}

我的学习经验

在 Node.js 生态系统中我使用 MySQL2🔗 与本地数据库沟通,但 SQL 返回的数据没有 TypeScript 类型是一大问题,这个问题通常通过 ORM 框架通过定义好的 Schema 来生成 TypeScript 类型。

发现 ORM 之间的经历转移麻烦且上手难度高,既要理解 ORM 的抽象,又要在必要时能理解其背后的 SQL 会是很头疼的问题,因此我会选择尽可能少量抽象的 SQL Builder 来熟悉 SQL 语法,同时享受它带来的便利,在 TS 生态系统里面推荐 Drizzle ORM🔗 有很完善的 SQL Builder ~ ORM 体验。

总结

特性SQLSQL BuilderORM
操作弹性
学习门槛
安全性低(自行防范)
性能掌控最高
适合场景着重性能通用增删查改为主

延伸阅读