使用Golang及时提交数据库

时间:2018-01-23 12:42:06

标签: database go

我成功了#34;批次"许多500-1000行的语句一次插入。然而,这是使用简单的for循环并手动将其设置为500-1000循环。类似的东西:

for i:=0;i<500;i++ {
   // Create a string of 500 values to be inserted at once
}
// Insert the 500 values

有没有一种方法可以及时commit()喜欢:&#34;提交每一秒&#34; ?

从概念上讲,我希望有类似的东西;

// Create connection to DB
// Begin a transaction
// Prepare a statement

go timelyCommits(tx)  // spawn a commit ticker
for {
   // Constantly create string of values to be inserted like:
   // Values (1, "one"),(2,"two"),(3,"three")...(1000,"thousand")...
   // Insert without commit
}

func timelyCommits(tx){
   for {
      time.Sleep(1 * time.Second)
      tx.Commit()
   }
}

2 个答案:

答案 0 :(得分:1)

优化不是一项微不足道的任务,也可能涉及数据库调优等。如果没有关于您尝试实施的系统的详细信息,很难给出适当的建议。除了答案中已经建议的内容之外,您可能还需要实现一种缓冲,例如:具有固定容量的通道。然后,当 buffer FULL或计时器EXPIRED时,构建查询,然后在事务中执行BULK INSERT。在The Go Playground处尝试。

package main

import (
    "fmt"
    "time"
)

type DataBuffer struct {
    capacity int
    duration time.Duration

    incomingData chan interface{}
    full chan bool
    mustExit chan bool
    done chan bool

    query string
    args []interface{}
}

func NewDataBuffer(capacity int, dur time.Duration) *DataBuffer {
    buf := &DataBuffer {
        incomingData: make(chan interface{}, capacity),
        full: make(chan bool),
        args: make([]interface{}, capacity),
        duration: dur,
        mustExit: make(chan bool, 1),
        done: make(chan bool, 1),
    }
    return buf
}

func (b *DataBuffer) Append(d interface{}) {
    if !b.put(d) {
        //Notify that buffer is full
        //<- will wait until space available
        b.full <- true
        b.incomingData <- d
    }
}

func (b *DataBuffer) put(d interface{}) bool {
    //Try to append the data
    //If channel is full, do nothing, then return false
    select {
    case b.incomingData <- d:
        return true
    default:
        //channel is full
        return false
    }
}

func (b *DataBuffer) execTransaction() error {
    /*
        Begin transaction
        Insert Data Group 
        Commit/rollback
    */

    fmt.Print(time.Now())
    fmt.Println(b.query)
    fmt.Println(b.args)

    return nil
}

func (b *DataBuffer) clear() {
    //clear args
    nOldArg := len(b.args)
    for k := 0; k < nOldArg; k++ {
        b.args[k] = nil
    }
    b.args = b.args[:0]
    b.query = ""
}

func (b *DataBuffer) buildQuery() bool {
    ndata := len(b.incomingData)
    if ndata == 0 {
        return false
    }

    k := 0
    b.clear()

    //Build the query, adjust as needed
    b.query = "QUERY:"
    for data := range b.incomingData {
        b.query += fmt.Sprintf(" q%d", k) //build the query
        b.args = append(b.args, data)

        k++
        if k >= ndata {
            break
        }

    }
    return true
}

func (b *DataBuffer) doInsert() {
    if b.buildQuery() {
        b.execTransaction()
    }
}

func (b *DataBuffer) runAsync() {
    defer func() {
        b.doInsert()
        fmt.Println("Last insert")
        close(b.done)
    }()

    timer := time.NewTimer(b.duration)
    for {
        select {
        case <- timer.C:
            b.doInsert()
            fmt.Println("Timer Expired")
            timer.Reset(b.duration)
        case <- b.full:
            if !timer.Stop() {
                <-timer.C
            }
            b.doInsert()
            fmt.Println("Full")
            timer.Reset(b.duration)
        case <- b.mustExit:
            if !timer.Stop() {
                <-timer.C
            }
            return  
        }
    }
}

func (b *DataBuffer) Run() {
    go b.runAsync()
}
func (b *DataBuffer) Stop() {
    b.mustExit <- true
}

func (b *DataBuffer) WaitDone() {
    <- b.done
}

func main() {
    buf := NewDataBuffer(5, 1*time.Second)
    buf.Run()

    //simulate incoming data
    for k := 0; k < 30; k++ {
        buf.Append(k)
        time.Sleep(time.Duration(10*k)*time.Millisecond)
    }
    buf.Stop()
    buf.WaitDone()  
}

注意:

  • 您需要实施正确的错误处理。
  • incomingData的类型可根据您的需要进行调整

答案 1 :(得分:0)

您可以使用执行以下操作的goroutine执行此操作:

  • 创建新交易并准备报表
  • 从输入通道连续读取(这会从程序的其他部分接收数据)
  • 在10ms后超时并提交交易,然后创建一个新的以累积更多数据

此功能可能类似于:

func transactionLoop(input chan Data) {
    tx := CreateNewTransaction() // This creates a new TX and prepares statements
    timeout := time.NewTimer(time.Millisecond * 10)
    for {
        select {
        case newData <- input:
            tx.Insert(newData)
        case <- timeout.C:
            // Too much time has passed: commit and create new tx.
            tx.Commit()
            // Create new transaction and launch timer.
            tx = CreateNewTransaction()
            timeout.Reset(time.Millisecond * 10)
        }
    }
}

这可能会做你想要的,虽然还有很多细节/问题:

  • 绝对没有将错误传递回来电者的概念:这是一个糟糕的主意
  • 不应提交空交易
  • 如果两个通道同时准备好,则订单是随机的(应该没问题)
  • 您可能想要在另一个goroutine中提交,但如果您的提交时间超过10毫秒,则可能会遇到麻烦。
  • 我没有测试过这个,只记下事情