2016-10-01 4 views
0

を書く私はそれが2つの同時トランザクションを実行し、これはショーGolang における場合や、ここhttps://play.golang.org/p/2fGKEyh0Wlmysqlの安全な読み込み - 更新 -

で、MySQLのロック・レコードのメカニックを理解しよう、と彼らは同じ行の更新を読んで
- 最初のトランザクションが、行をロック(3秒間スリープ)何か
をしようとします - 2番目は、同じキー

テストソースコードの更新を読み取ろう

package main 

import (
    "github.com/jmoiron/sqlx" 
    "github.com/satori/go.uuid" 
    "log" 
    "sync" 
    "time" 
    _ "github.com/go-sql-driver/mysql" 
) 

type Wallet struct { 
    ID  string 
    Balance int64 
} 

func main() { 
    db, err := sqlx.Connect("mysql", "root:[email protected](mysql:3306)/test?parseTime=true") 
    if err != nil { 
     log.Println(err) 
     return 
    } 
    db.Exec(`CREATE TABLE test_wallet (
    id varchar(64) NOT NULL, 
    balance bigint(20) DEFAULT NULL, 
    PRIMARY KEY (id) 
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
    `) 
    var wg sync.WaitGroup 
    wg.Add(2) 

    wID := uuid.NewV4().String() 
    db.Exec("INSERT INTO test_wallet (id,balance) VALUES (?,?)", wID, 10) 

    go func() { 
     defer wg.Done() 
     tx, err := db.Beginx() 
     w1 := &Wallet{} 
     err = db.Get(w1, "SELECT * FROM test_wallet WHERE id=? FOR UPDATE", wID) // read and lock the record 
     if err != nil { 
      log.Println(err) 
     } 
     log.Printf("got %+v on r1\n", w1) 
     time.Sleep(time.Second * 3) 
     res, err := tx.Exec("UPDATE test_wallet SET balance=? WHERE id=?", w1.Balance+5, wID) 
     if err != nil { 
      log.Println(err) 
     } 
     n, err := res.RowsAffected() 
     if n != 1 { 
      log.Println("update not affected r1") 
     } 
     tx.Commit() 
     log.Println("done on r1") 
    }() 

    time.Sleep(time.Second) // make sure go-routine lock `id` row 

    go func() { 
     defer wg.Done() 
     tx, err := db.Beginx() 
     w2 := &Wallet{} 
     err = db.Get(w2, "SELECT * FROM test_wallet WHERE id=? FOR UPDATE", wID) 
     if err != nil { 
      log.Println(err) 
     } 
     log.Printf("got %+v on r2\n", w2) 
     res, err := tx.Exec("UPDATE test_wallet SET balance=? WHERE id=?", w2.Balance+7, wID) 
     if err != nil { 
      log.Println(err) 
     } 
     n, err := res.RowsAffected() 
     if n != 1 { 
      log.Println("update not affected r2") 
     } 
     tx.Commit() 
     log.Println("done on r2") 
    }() 
    wg.Wait() 
    w := &Wallet{} 
    err = db.Get(w, "SELECT * FROM test_wallet WHERE id=?", wID) 
    if err != nil { 
     log.Println(err) 
    } 
    log.Printf("%+v\n", w) 
} 
私の端末

2016/10/01 09:57:00 got &{ID:aab7165c-4b3b-406d-b1d0-caf3f45f72be Balance:10} on r1 
2016/10/01 09:57:01 got &{ID:aab7165c-4b3b-406d-b1d0-caf3f45f72be Balance:10} on r2 
2016/10/01 09:57:01 done on r2 
2016/10/01 09:57:03 done on r1 
2016/10/01 09:57:03 &{ID:aab7165c-4b3b-406d-b1d0-caf3f45f72be Balance:15} 

から

結果、第2のルーチンがロックされていないように見えます?

+4

:あなたはすなわち

err = tx.Get(w1, "SELECT * FROM test_wallet WHERE id=? FOR UPDATE", wID) // read and lock the record err = tx.Get(w2, "SELECT * FROM test_wallet WHERE id=? FOR UPDATE", wID) 

修正した後、私は次の結果を得た、クエリを実行するためにtxを使用する必要があります。競争状態に陥り、最終的にあなたの顔に爆破するようになっているメカニズムであるロックを緩和しようとしています。お金やお金のような通貨を含むものについては、**常に**個々の借方とクレジットを記録します。**決して過去の情報に基づいて残高を直接決めることはありません。代わりに 'SET balance = balance +?'のようなことを行います。これは原子的で*は*働きます。原子性と完全性を確保するために、ロックではなく**トランザクション**を使用してください。 – tadman

+0

ええ、私は、トランザクション*(これは 'tx.Beginx'でこれを使っていますが、mysqlの分離レベルを' SERIALIZABLE'に設定していますが、まだ期待通りに動作しませんでした。外部の行を読み取って何かを検証し、新しい値で更新する必要があります。最後のクエリからバランスが変更されていないことを確認する必要があります。これをアーカイブする方法はありますか? – secmask

+0

'balance'を更新する前に、 SETバランス=バランス+?... – secmask

答えて

2

トランザクションを誤って使用しました。 dbは取引ではではなく、txのみです。 dbがトランザクションに包まれていないので、だから、

err = db.Get(w1, "SELECT * FROM test_wallet WHERE id=? FOR UPDATE", wID) // read and lock the record 
err = db.Get(w2, "SELECT * FROM test_wallet WHERE id=? FOR UPDATE", wID) 

された第1および第2ゴールーチン内のステートメントは、行をロックしません。あなた自身を開いているので、これはバランスの更新をしないかの教科書の一例である

2016/10/01 13:26:10 got &{ID:6ff45acd-701c-458f-a17f-84cc4e982c80 Balance:10} on r1 
2016/10/01 13:26:14 done on r1 
2016/10/01 13:26:14 got &{ID:6ff45acd-701c-458f-a17f-84cc4e982c80 Balance:15} on r2 
2016/10/01 13:26:14 done on r2 
2016/10/01 13:26:14 &{ID:6ff45acd-701c-458f-a17f-84cc4e982c80 Balance:22} 
+0

それは正しいです、thx – secmask

+2

@secmark、[このチュートリアル](http://go-database-sql.org/)をよく読んで理解してください。仕事へのアプローチデータベースとの関係は、他の一般的なプラットフォームとは異なります。これらの違いを認識する必要があります。 – kostix

関連する問題