cch123 / blog_comment

comments of xargin.com
8 stars 0 forks source link

为什么要旗帜鲜明地反对 orm 和 sql builder #254

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

为什么要旗帜鲜明地反对 orm 和 sql builder

http://xargin.com/you-should-avoid-orm-and-sql-builder/

leafduo commented 2 years ago

我们也在用 sqlc,原因有几个:

  1. ORM 性能太差
  2. ORM 使得开发者不知道写出什么 SQL
  3. 直接用 database/sql 或者 sql builder 写起来比较痛苦

但是 sqlc 确实不太成熟,问题比较多,期待更好的工具

fishtreesugar commented 2 years ago

关于用 ORM 或者 SQL Builder debug 不方便的问题, 有两个办法可以缓解, 当然只是缓解:

QuantumGhost commented 2 years ago

FYI: https://google.github.io/sqlcommenter/

hicaoc commented 2 years ago

直接写sql灵活方便,性能好,查错也快

hanjk1234 commented 2 years ago

java的mybatis了解下,golang好像也有类似的实现。

cch123 commented 2 years ago

FYI: https://google.github.io/sqlcommenter/

这个厉害啊

cch123 commented 2 years ago

java的mybatis了解下,golang好像也有类似的实现。

Go 之前有一个,不过 Go 不像 java 那样能在注解里搞花活儿,写在注释里的东西说到底不是一个特别强的约束

cch123 commented 2 years ago

我们也在用 sqlc,原因有几个:

  1. ORM 性能太差
  2. ORM 使得开发者不知道写出什么 SQL
  3. 直接用 database/sql 或者 sql builder 写起来比较痛苦

但是 sqlc 确实不太成熟,问题比较多,期待更好的工具

我这边也还在调研,sqlc 除了对 mysql 支持不太好(比如不支持 in),还有啥问题么?

leafduo commented 2 years ago

我这边也还在调研,sqlc 除了对 mysql 支持不太好(比如不支持 in),还有啥问题么?

xianmian168 commented 2 years ago

本来觉得facebook的ent还不错的,看来也被曹大否了

cch123 commented 2 years ago

本来觉得facebook的ent还不错的,看来也被曹大否了

也没完全否定 orm,tob 场景 orm 并发量不高,或者公司内部平台 orm 挺好的,写着方便

主要是 toc 的 db 挂了影响面太大,得做事前预防,所以得搞这种 sql 方案

deepCyan commented 2 years ago

所以如果清楚的知道 ORM 会搞出来什么代码就没有问题咯 个人觉得代码还是语义化更好一些诶...毕竟是代码是给人读的,不是给机器读的,私以为牺牲一点点性能(非 DB 层)换来可读性的提高是划得来的 另外小声比比一下...我个人从 PHP 划到 GO,然后发现这语言哪有什么好用的 ORM 呀...

FlameMida commented 2 years ago

说实话Go的ORM甚至没有到好用的地步,还是sqlx或者sqlc写起来更舒服,但是可惜sqlc对MySQL支持不太完美

FlameMida commented 2 years ago

期待曹大的轮子

cch123 commented 2 years ago

所以如果清楚的知道 ORM 会搞出来什么代码就没有问题咯 个人觉得代码还是语义化更好一些诶...毕竟是代码是给人读的,不是给机器读的,私以为牺牲一点点性能(非 DB 层)换来可读性的提高是划得来的 另外小声比比一下...我个人从 PHP 划到 GO,然后发现这语言哪有什么好用的 ORM 呀...

自己的项目怎么都顺手,接手别人的找不着sql还是令人懊恼

minicloudsky commented 1 year ago

看到这里,深有感触,我司主要是java dubbo微服务,目前的sql审核做法是,通过匹配代码关键字,找到apollo namespace,一个namespace对应一个mysql数据源,然后解析mybatis xml文件,生成sql,然后用小米开源的soar进行sql审核,进行预执行,不过效果还是差点,主要是当一个项目中用到的数据源比较多时候,自动匹配数据源不太容易做到,最近还在想办法优化,有些sql因为生产环境qps,不会有什么问题,但是生产出现就很致命了,比如qps很高时候insert table on duplicate key update会导致死锁 soar mybatis-mapper2sql

cch123 commented 1 year ago

看到这里,深有感触,我司主要是java dubbo微服务,目前的sql审核做法是,通过匹配代码关键字,找到apollo namespace,一个namespace对应一个mysql数据源,然后解析mybatis xml文件,生成sql,然后用小米开源的soar进行sql审核,进行预执行,不过效果还是差点,主要是当一个项目中用到的数据源比较多时候,自动匹配数据源不太容易做到,最近还在想办法优化,有些sql因为生产环境qps,不会有什么问题,但是生产出现就很致命了,比如qps很高时候insert table on duplicate key update会导致死锁 soar mybatis-mapper2sql

你们已经做的很好啦哈哈

lesismal commented 10 months ago

可以试下我的sqlw,也是主张raw sql,不需要像sqlc那样去生成一大坨代码,只需要自己声明结构体这些,也不需要像直接使用标准库那样自己去for循环绑定。相比于sqlc,sqlw处理绑定时肯定是需要一些结构体反射的操作所以性能不如sqlc,但不必ORM性能更差,而且对于数据库操作、这点损失不算什么。 sqlw常规用法:

package main

import (
    "log"

    _ "github.com/go-sql-driver/mysql"
    "github.com/lesismal/sqlw"
)

const (
    sqlDropDatabase   = `drop database if exists sqlw_test`
    sqlCreateDatabase = `create database sqlw_test`
    sqlDropTable      = `drop table if exists sqlw_test.sqlw_test`
    sqlCreateTable    = `
        create table sqlw_test.sqlw_test (
        id bigint primary key auto_increment,
        i  bigint not null default 0,
        s  varchar(64) not null default ''
    )`
)

type Model struct {
    Id int64  `db:"id"`
    I  int64  `db:"i"`
    S  string `db:"s"`
}

func main() {
    db, err := sqlw.Open("mysql", "test:123qwe@tcp(localhost:3306)/mysql", "db")
    if err != nil {
        log.Fatalf("sqlw.Open failed: %v", err)
    }

    _, err = db.Exec(sqlCreateDatabase)
    if err != nil {
        log.Panic(err)
    }
    defer db.Exec(sqlDropDatabase)

    _, err = db.Exec(sqlCreateTable)
    if err != nil {
        log.Panic(err)
    }
    defer db.Exec(sqlDropTable)

    model := &Model{
        I: 1,
        S: "str_1",
    }
    result, err := db.Insert("insert into sqlw_test.sqlw_test", model)
    // result, err := db.Insert("insert into sqlw_test.sqlw_test(i,s)", &model) // insert the specified fields
    if err != nil {
        log.Fatalf("db.Insert failed: %v", err)
    }
    // print sql for debug
    log.Println("Insert sql:", result.Sql())

    var ret Model
    result, err = db.Select(&ret, "select * from sqlw_test.sqlw_test where id=?", 1)
    // result, err := db.Select(&model, "select (i,s) from sqlw_test.sqlw_test where id=?", selectId) // select the specified fields
    if err != nil {
        log.Fatalf("db.Select failed: %v", err)
    }
    log.Println("ret:", ret)
    // print sql for debug
    log.Println("Select sql:", result.Sql())

    newModel := &Model{
        I: 10,
        S: "str_10",
    }
    result, err = db.Update("update sqlw_test.sqlw_test set i=?, s=? where id=?", newModel, 1)
    if err != nil {
        log.Fatalf("db.Update failed: %v", err)
    }
    // print sql for debug
    log.Println("Update sql:", result.Sql())

    result, err = db.Delete("delete from sqlw_test.sqlw_test where id=?", 10)
    if err != nil {
        log.Fatalf("db.Delete failed: %v", err)
    }
    // print sql for debug
    log.Println("Delete sql:", result.Sql())
}
cch123 commented 10 months ago

可以试下我的sqlw,也是主张raw sql,不需要像sqlc那样去生成一大坨代码,只需要自己声明结构体这些,也不需要像直接使用标准库那样自己去for循环绑定。相比于sqlc,sqlw处理绑定时肯定是需要一些结构体反射的操作所以性能不如sqlc,但不必ORM性能更差,而且对于数据库操作、这点损失不算什么。 sqlw常规用法:

package main

import (
  "log"

  _ "github.com/go-sql-driver/mysql"
  "github.com/lesismal/sqlw"
)

const (
  sqlDropDatabase   = `drop database if exists sqlw_test`
  sqlCreateDatabase = `create database sqlw_test`
  sqlDropTable      = `drop table if exists sqlw_test.sqlw_test`
  sqlCreateTable    = `
      create table sqlw_test.sqlw_test (
      id bigint primary key auto_increment,
      i  bigint not null default 0,
      s  varchar(64) not null default ''
  )`
)

type Model struct {
  Id int64  `db:"id"`
  I  int64  `db:"i"`
  S  string `db:"s"`
}

func main() {
  db, err := sqlw.Open("mysql", "test:123qwe@tcp(localhost:3306)/mysql", "db")
  if err != nil {
      log.Fatalf("sqlw.Open failed: %v", err)
  }

  _, err = db.Exec(sqlCreateDatabase)
  if err != nil {
      log.Panic(err)
  }
  defer db.Exec(sqlDropDatabase)

  _, err = db.Exec(sqlCreateTable)
  if err != nil {
      log.Panic(err)
  }
  defer db.Exec(sqlDropTable)

  model := &Model{
      I: 1,
      S: "str_1",
  }
  result, err := db.Insert("insert into sqlw_test.sqlw_test", model)
  // result, err := db.Insert("insert into sqlw_test.sqlw_test(i,s)", &model) // insert the specified fields
  if err != nil {
      log.Fatalf("db.Insert failed: %v", err)
  }
  // print sql for debug
  log.Println("Insert sql:", result.Sql())

  var ret Model
  result, err = db.Select(&ret, "select * from sqlw_test.sqlw_test where id=?", 1)
  // result, err := db.Select(&model, "select (i,s) from sqlw_test.sqlw_test where id=?", selectId) // select the specified fields
  if err != nil {
      log.Fatalf("db.Select failed: %v", err)
  }
  log.Println("ret:", ret)
  // print sql for debug
  log.Println("Select sql:", result.Sql())

  newModel := &Model{
      I: 10,
      S: "str_10",
  }
  result, err = db.Update("update sqlw_test.sqlw_test set i=?, s=? where id=?", newModel, 1)
  if err != nil {
      log.Fatalf("db.Update failed: %v", err)
  }
  // print sql for debug
  log.Println("Update sql:", result.Sql())

  result, err = db.Delete("delete from sqlw_test.sqlw_test where id=?", 10)
  if err != nil {
      log.Fatalf("db.Delete failed: %v", err)
  }
  // print sql for debug
  log.Println("Delete sql:", result.Sql())
}

sqlc 能够在照顾强类型约束流程的前提下,做到任意 sql 上线前的审计,你这个不太行

lesismal commented 10 months ago

sqlc 能够在照顾强类型约束流程的前提下,做到任意 sql 上线前的审计,你这个不太行

审计是指sqlc自己解析sql语句的时候自带了审计?如果是指这个,那sqlw确实没有

对于raw sql,我们团队审计流程通常是开发自审、审计平台+dba审,复杂的还要单独会议多工种评审。 单独靠sql语句的分析、审计平台这些自动进行审计也是不足够可靠的,性能敏感的场景需要人工,而且还要加适当的测试,不敢让审计平台上自动流程就上限 我这个也是raw sql,所以语句都是可见,这种审计流程不是问题

cch123 commented 10 months ago

sqlc 能够在照顾强类型约束流程的前提下,做到任意 sql 上线前的审计,你这个不太行

审计是指sqlc自己解析sql语句的时候自带了审计?如果是指这个,那sqlw确实没有

对于raw sql,我们团队审计流程通常是开发自审、审计平台+dba审,复杂的还要单独会议多工种评审。 单独靠sql语句的分析、审计平台这些自动进行审计也是不足够可靠的,性能敏感的场景需要人工,而且还要加适当的测试,不敢让审计平台上自动流程就上限 我这个也是raw sql,所以语句都是可见,这种审计流程不是问题

人工就太 low 了,要 sql parser 把 sql 扫出来,和 information schema 和 sys 库这些里的 index 信息去做匹配的,都是很基本的东西了

lesismal commented 10 months ago

人工就太 low 了,要 sql parser 把 sql 扫出来,和 information schema 和 sys 库这些里的 index 信息去做匹配的,都是很基本的东西了

所以自动审直接能搞定所有sql性能问题吗?求方案推荐,我目前还没用到过这么高级的自动审,大表海量数据我们都还是要人工加持,审核平台只做基本的分析审核

sqlc可以直接审出、搞定大表性能问题吗?

lesismal commented 10 months ago

我猜sqlc或者你说的index信息去做匹配的这些,也没办法做大线上大表数据量级、线上真实并发量的真实评估吧?未来要是AI加持了我倒是相信有这么一天。 现阶段的审核平台,不人工加持一道,我是不敢允许大表或者复杂sql的上线

cch123 commented 10 months ago

我猜sqlc或者你说的index信息去做匹配的这些,也没办法做大线上大表数据量级、线上真实并发量的真实评估吧?未来要是AI加持了我倒是相信有这么一天。 现阶段的审核平台,不人工加持一道,我是不敢允许大表或者复杂sql的上线

可以的。。。云厂商的 db 专家系统就是干这个的

lesismal commented 10 months ago

那也不是它sqlc直接能干吧。。。不还是得sql语句本身吗。。。

jiangbaiyan commented 3 months ago

关于从SQL反查链路这个case,可以往sql comment里注入trace_id

cch123 commented 1 month ago

关于从SQL反查链路这个case,可以往sql comment里注入trace_id

很 6

f0rb commented 1 month ago

作为DBA写出这样的文章一点都不稀奇,不干研发的活就不会从软件工程的角度考虑问题。

软件开发最重要的是封装和重用,SQL作为静态文本语言,一条SQL语句只能代表一种查询需求。遇到多个查询条件需要按照用户传参进行拼接的话,是不可能把所有组合出的SQL语句写代码里的,为了代码复用会用if语句拼接需要的查询条件,所以即使不用ORM和SQL Builder,也不可能直接搜到完整的SQL。 另外,调用复杂的情况下,即使代码里存在一模一样的SQL语句,也是需要查看代码寻找调用出处的。

DBA不写代码,不懂里面的因果关系,就把问题在于 slow query 中的 SQL 很难与代码直接关联起来这样的问题归因到ORM和SQL builder,显然是没把原因搞清楚。 遇到慢SQL还是把SQL打到日志里去,通过测试用例覆盖去找SQL语句,而不是开历史倒车,怪到ORM和Builder上。