mysql并发问题记录
2015-10-05 19:35
597 查看
并发更新问题
悲观锁假如有这样一张表:
| 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会立刻将结果进行提交。本文代码中显式的使用事务操作。
相关文章推荐
- MySQL中的integer 数据类型
- MySQL存储过程
- mysql中int、bigint、smallint 和 tinyint的区别与长度
- mysql load data 导出、导入 csv
- source命令执行SQL脚本文件
- MySQL创建用户及权限控制
- MySQL管理数据表
- linux下mysql添加用户
- mysql procedure
- mysql触发器
- MySQL 备份和恢复策略
- mac下安装mysql(转载)
- mysql 修改编码 Linux/Mac/Unix/通用(杜绝修改后无法启动的情况!)
- MySQL数据的导出、导入(mysql内部命令:mysqldump、mysql)
- mysql数据行转列
- Linux下修改MySQL编码的方法
- MySQL Server 日志
- MySQL 安全事宜
- MySQL 备份与恢复