什么时候在golang Web服务器上准备postgresql语句?

时间:2014-07-04 06:31:54

标签: postgresql go

我有一个连接到postgresql数据库的Web服务器。据我所知,postgresql驱动程序在内部管理连接池,因此我将数据库连接作为全局变量。

我正在使用预备语句,我不知道在服务器启动之前在我的main函数中提前准备它们是否是一个好主意,或者在我的请求处理程序中执行它们(如下所示)。我是golang的新手。我认为使语句全局化更有效,但我不确定。请帮忙。

var db *sql.DB

func main() {
  router = pat.New()
  router.Get("/", handler)
  db, e := sql.Open("postgres", "...")
  ...
  http.ListenAndServe("127.0.0.1", router)
}

func handler(w http.ResponseWriter, r *http.Request) {
  s, e := db.Prepare("select * from mytable where field=$1")
  r, e := s.Exec(123)
  ...
}

2 个答案:

答案 0 :(得分:9)

这完全取决于您的使用案例。根据经验,我会说你应该在运行服务器之前准备好你的语句,原因有多种:

  • 如果语句未正确准备,您可能无法启动。如果您即时准备它们,失败的声明可能会在整个程序启动后很长时间内无效。
  • 如果事先准备它们,则不必处理并发:如果需要时进行准备,则必须使用同步机制来确保不会多次并行准备语句,这将以SQL服务器结束火热的......
  • 处理起来更简单。

至于你的数据库句柄,你应该使语句全局化,以便随时使用它们而不必传递指针。如果您发现自己处理许多语句,例如超过10-15(任意数字),您可能会发现将所有与数据库相关的内容(数据库初始化,查询等)放入主包的子包中会更容易

答案 1 :(得分:1)

请注意,根据我的阅读材料和个人经验,这是一篇较长的文章,它将成为我撰写的博客文章的基础。我将尝试使用我正在开发的API服务中的示例来解释这些概念

准备陈述的优点

我尝试在启动应用程序时准备好所有语句。这可以有一些性能优势,但正如在另一个答案中所提到的,这将检查语句是否有效并且可能无法启动。结合Kubernetes上的一些自我修复,我已经能够避免部署损坏的语句(由于额外的,)并回滚到我的应用程序的已知版本。

全局变量的缺点

尽管如此,我不建议使用全局变量来管理您准备好的语句。这些很难测试,并且难以进行重构和扩展您的应用程序。相反,我使用下面的模式:

定义服务interface

通过定义可以传递给函数的接口,我们可以轻松地模拟实现以进行测试。

// ExampleRetrievalService defines methods for retrieval
// of examples
type ExampleRetrievalService interface {
    // All returns all examples for a particular userID
    All(userID string) ([]Example, error)
}

接受http.Handler

中的界面

通过编写接受interface的处理程序,我们可以轻松地传递不同的实现。例如postgres支持的实施或redismysql

// ExampleHandler takes an implementation of ExampleRetrievalService and uses it to return a list of Example objects
func ExampleHandler(exampleService ExampleRetrievalService) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        examples, err := exampleService.All(getUserID(r))
        [...]
    })
}

使用数据库实施服务

postgres包中,我使用数据库支持的结构实现服务。请注意,属性是预处理语句

type exampleRetrievalService struct {
    retrieveAllStatement    *sqlx.Stmt
}

func (s *exampleRetrievalService) All(userID string) ([]ex.Example, error) {
    var examples []ex.Example
    return examples, s.retrieveAllStatement.Select(&examples, userID)
}

在服务实施创建,准备语句

const (
    selectAllForUserID = `SELECT * FROM example WHERE user_id=$1`
)

// NewExampleRetrievalService takes a `*sqlx.DB` and returns an implementation of
// `ex.ExampleRetrievalService` backed by PostgreSQL
func NewExampleRetrievalService(db *sqlx.DB) (*exampleRetrievalService, error) {

    retrieveAllStatement, err := db.Preparex(selectAllForUserID)

    if err != nil {
        errors.Wrap(err, "unable to prepare database transactions")
    }
    return &exampleRetrievalService{
        retrieveAllStatement:    retrieveAllStatement,
    }, err
}

使用数据库创建功能

在我的main.go我要做的就是创建一个新的服务实现并将其传递给相关的功能。这些现在可以使用模拟实现进行测试,如果需要,我可以轻松地交换每个函数的实现。

func main() {
    [...]
    db = sqlx.MustConnect("postgres", viper.GetString(keyPostgresConn))
    exampleRetrievalService, err = postgres.NewExampleRetrievalService(db)
    [...]
    r.Handle("/", ExampleHandler(exampleRetrievalService))
}

在服务初始化时使用预准备语句保证至少我的SQL语句是正确的。定义我传递给处理程序而不是全局变量的接口意味着我可以为集成测试创建测试postgres实现。这可能看起来有点额外的工作,但这种结构提供了足够的灵活性来处理简单和非常复杂的系统。