lots of prepared statements, how do I deal with them?

xuanbao · · 558 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Hey all,</p> <p>Currently I&#39;m working on a project where I do a lot of database calls. The queries use variables outside of the program, but still by a program I control. To be save I used prepared statements for everything that uses variables that are not created inside the program.</p> <p>I end up with a function like this:</p> <pre><code>func createStmts() (*sql.Stmt, *sql.Stmt, *sql.Stmt, *sql.Stmt, *sql.Stmt) { stmtTld, err := db.Prepare(&#34;SELECT id FROM top_level_domain WHERE name = $1&#34;) if err != nil { log.Println(err) } stmtInsert, err := db.Prepare(`INSERT INTO domain_name (name, tld_id, alive, ip_address, has_robots, redirect_id) VALUES ( $1, $2, NULL, NULL, NULL, NULL )`) if err != nil { log.Println(err) } stmtExists, err := db.Prepare(&#34;SELECT id, has_been_scannend, in_queue FROM domain_name WHERE name = $1&#34;) if err != nil { log.Println(err) } stmtDomainID, err := db.Prepare(&#34;SELECT id FROM domain_name WHERE name = $1&#34;) if err != nil { log.Println(err) } stmtInsertExternalLink, err := db.Prepare(&#34;INSERT INTO domain_name_external_links (domain_name_id, external_domain_id) VALUES ($1, $2)&#34;) if err != nil { log.Println(err) } return stmtTld, stmtInsert, stmtExists, stmtDomainID, stmtInsertExternalLink } </code></pre> <p>The function that uses these statements loops over an array with structs, again prepared statments make sense here becuase they are used multiple times before being closed.</p> <ol> <li>How can I avoid this &#34;utility&#34; function that keeps on growing? Should I split it up, each statments gets its own function?</li> <li>How about naming them? stmtInsert, stmtInsertDomain, DomainInsert?</li> </ol> <p>Sorry I&#39;m just a bit lost at about this.</p> <p>Thanks in advance.</p> <hr/>**评论:**<br/><br/>willglynn: <pre><blockquote> <p>To be save I used prepared statements for everything that uses variables that are not created inside the program.</p> </blockquote> <p>You don&#39;t need <code>Prepare()</code> for this. <a href="https://golang.org/pkg/database/sql/#DB.Query" rel="nofollow"><code>db.Query()</code> takes parameters</a> and can use placeholders to provide the exact same level of safety. <code>Query()</code> literally prepares the statement and immediately executes it, unless the driver provides a <code>driver.Queryer</code> interface with equivalent semantics. <code>db.Query()</code> and <code>db.Prepare()</code> provide the exact same safety from SQL injection.</p> <p>Why two interfaces, then? Prepared statements allow the database to cut down on query planning time, a cost which can get significant for queries that are executed many many times. Unfortunately, moving all your queries to a central place like a <code>func createStmts()</code> can have a significant negative impact on code structure and general readability. Is there some option in the middle?</p> <p><code>pgx</code> thinks so, namely <a href="https://godoc.org/github.com/jackc/pgx#ConnPool.Prepare" rel="nofollow">an idempotent <code>Prepare()</code> function</a>. This lets you write code that prepares and execute statements as needed -- keeping the SQL queries in the code that uses them -- but subsequent <code>Prepare()</code> calls for identical queries are handled from a cache. This gives the performance benefits of preparing once and executing repeatedly without the maintenance drawbacks. If you need prepared statements, I would encourage you to do something similar on top of <code>database/sql</code>.</p></pre>j0holo: <pre><p>Aha, not needing the prepare statement makes the code a lot cleaner. Each database function gets its own go function. Removes a bunch of statement arguments from my functions.</p> <p>Thanks a lot for your advice.</p></pre>lluad: <pre><p>Yes, all of this.</p> <p>Also, you should be aware that prepared statements may be slower, sometimes drastically slower, than non-prepared statements. You save time on the planning, but the tradeoff is that the &#34;generic&#34; plans that are produced may not be as efficient as one that&#39;s planned with the knowledge of the parameters.</p> <p>Parameterized queries are always a good idea, if you can use them, but prepared queries aren&#39;t <em>always</em> better.</p></pre>j0holo: <pre><p>Never knew prepared statements could be slower. I thought that prepared statements optimized the query in advance somehow.</p></pre>mcouturier: <pre><p>I may talk out of my ass but didn&#39;t prepared statements also reserve a connection right away?</p></pre>lluad: <pre><p>At the database level, typically yes. The statement is prepared on a single connection and can only be used there (at least on the RDBMs I&#39;m familiar with).</p> <p>But the Go db library - database/sql - provides pooling, and abstracts away the prepared statements. The db.Prepare() you run just tells the library about the statement, and it&#39;ll then run real prepare statements on each underlying session as needed (or it won&#39;t - there&#39;s no obligation for db.Prepare() to map on to an underlying PREPARE statement, though I&#39;d usually expect it to). I&#39;ve not looked at the code, but I&#39;d expect it to do that lazily.</p></pre>piatochkin: <pre><p>I did it like this</p> <pre><code>const ( stmtCreateUser = iota stmtGetUser stmtUpdateUser ) var stmtPairs = []struct { id int stmt string }{ {stmtGetUser, `SELECT u.email, u.data FROM user u WHERE u.id=$1 FOR UPDATE`}, {stmtUpdateUser, `UPDATE user SET data=$1 WHERE id=$2`}, } func (d *DB) prepareStmts() error { for _, stmtPair := range stmtPairs { st, err := d.DB.Prepare(stmtPair.stmt) if err != nil { return fmt.Errorf(&#34;Failed to prepare statement %s: %s&#34;, stmtPair.stmt, err.Error()) } d.stmts[stmtPair.id] = st } return nil } // NewDB creates new Postgres connection func NewDB(dataSource string) (*DB, error) { db, err := sql.Open(&#34;postgres&#34;, dataSource) if err != nil { return nil, err } res := &amp;DB{DB: db, stmts: map[int]*sql.Stmt{}} if err := res.prepareStmts(); err != nil { return nil, err } return res, nil } </code></pre></pre>j0holo: <pre><p>Okay thanks alot, this looks a lot cleaner. Will take a look at it tomorrow. Thanks</p></pre>tcrypt: <pre><p>database/sql will always use prepared statements when you pass in parameters; no need to make them manually.</p></pre>mastercactapus: <pre><pre><code>type Backend struct { db *sql.DB insert *sql.Stmt delete *sql.Stmt } func NewBackend(db *sql.DB) (*Backend, error) { var err error var s *sql.Stmt prep := func(q string) *sql.Stmt { if err != nil { return nil } s, err = db.Prepare(q) // err = errors.Wrapf(err, &#34;prepare query: %s&#34;, q) return s } return &amp;Backend{ db: db, insert: prep(&#34;insert ...&#34;), delete: prep(&#34;delete ...&#34;), }, err } </code></pre> <p>If you have a lot (more than one &#34;Backend&#34;-type struct) it works better to break it out into a struct that has db, err and a &#34;Prepare&#34; method (whatever you want to call it).</p> <p>The idea is to store the first error and jump out if it&#39;s != nil. It makes subsequent calls after the first failure cheap enough that it doesn&#39;t matter that you don&#39;t check the error until the end. It&#39;s not the right choice for <em>every</em> situation, but it&#39;s good for initialization and places where you can tolerate a little inefficiency to improve clarity.</p> <p>Inspiration came from bufio.Scanner and how it &#34;stores&#34; the error state: <a href="https://golang.org/pkg/bufio/#Scanner" rel="nofollow">https://golang.org/pkg/bufio/#Scanner</a></p> <p>There may have been a blog post at some point in the past too, I vaguely remember reading something. If I find it I&#39;ll post the link.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

558 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传