mysql并发问题记录

来源:互联网 发布:数据库的分类 编辑:程序博客网 时间:2024/05/29 09:23

并发更新问题

悲观锁
假如有这样一张表:
| id |name |price |

每一行代表一个拍卖的商品,用户每次投标价格加1,初始价格为0, 有100个用户进行抢拍,每个用户进行了一次投标,那么最终的价格应该是100

对于上面这种情况,对于用户的每次投标,我们一般会先SELECT查出这条记录,然后根据查出记录的Price,加1 再UPDATE 。

如果所有的用户在几乎同时去投标,那么很可能大家在同一时间取出初始值,然后加1更新,最终的实际价格可能会远远小于100.

测试时模拟100个goroutine进行并发更新,共测试10次,看看测试结果。

func main() {    test()}

//测试10次看看结果

func test() {    db, err := sql.Open("mysql", "mysql:123@/test")    if err != nil {        log.Fatal(err)    }    defer db.Close()    var result int    var n int = 10    for i := 0; i < n; i++ {        if checkRight(db) {            result++        }    }    fmt.Printf("test %d times, %d times correct", n, result)}

//开启100个协程进行并发更新,看看结果是否正确

func checkRight(db *sql.DB) bool {    db.Exec("create table mytable(id integer primary key auto_increment  not null, name text, price integer );")    defer db.Exec("drop table mytable;")    stmt_insert, err := db.Prepare("insert into mytable(name,price) values(?,?)")    if err != nil {        log.Fatal(err)    }    _, err = stmt_insert.Exec("iphone6", 0)//初始价格为0,理论上100个人各投标一次后价格为100    if err != nil {        log.Fatal(err)    }    var wg sync.WaitGroup    times := 100    for i := 0; i < times; i++ {        wg.Add(1)        go func() {            tx, _ := db.Begin()            row := tx.QueryRow("select price from mytable where name=?", "iphone6")            var price int64            row.Scan(&price)            price++            _, err := tx.Exec("update mytable set price=? where name=?", price, "iphone6")            if err != nil {                log.Fatal(err)            }            tx.Commit()            wg.Done()        }()    }    wg.Wait()    row := db.QueryRow("select price from mytable where name=?", "iphone6")    var price int64    row.Scan(&price)    fmt.Println("bid times: ", times, " price: ", price)    if times == int(price) {        return true    }    return false}

10次执行结果如下:
这里写图片描述

可见测试10次,结果因为没有处理并发的原因,实际值与期望相差太远。

这种情况下悲观锁就起作用了,先看度娘的解释:

悲观锁,正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

也就是说,大家在投标时,当某个用户在投标过程中(从执行select * for table_name for update;到commit数据),该条数据被锁定了,直到更新后(执行commit)才解锁,其他用户才能查询到数据。这样其他用户查询到的总是上一个用户更新后的价格,从而实现了更新操作的序列化。

使用悲观锁很简单,在选择要更新的数据时,加上for update。
修改checkRight函数:

    ...    for i := 0; i < times; i++ {        wg.Add(1)        go func() {            tx, _ := db.Begin()            row := tx.QueryRow("select price from mytable where name=? for update", "iphone6")//注意此查询语句中的for update            var price int64            row.Scan(&price)            price++            _, err := tx.Exec("update mytable set price=? where name=?", price, "iphone6")            if err != nil {                log.Fatal(err)            }            tx.Commit()            wg.Done()        }()    }    wg.Wait()    ...

再执行看看结果:

这里写图片描述
可以看到执行结果与期望完全符合。

*注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。本文代码中显式的使用事务操作。

0 0