mysql并发问题记录

并发更新问题

悲观锁
假如有这样一张表:
| 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值