数据库并发处理

使用 Apache Bench 来模拟并发,使用如下命令:

$ ab -c 100 -n 1000 http://127.0.0.1/a.php

-c 100 -n 1000 表示一共有1000个请求,每次并发请求100个。

Mysql

我们模拟一次投票,表结构只有一个vote字段,用来记录投票数,每次运行脚本该字段加1:

QQ20170219-135642.png

Mysql的update语句可以保证并发数据的一致性,代码如下:

mysql_query('UPDATE table1 SET vote=vote+1 WHERE id=1',$con);

然后使用 Apache Bench 模拟并发:

$ ab -c 100 -n 1000 http://127.0.0.1/a.php

查看数据库结果:

QQ20170219-140045.png

可以看到数据为1000,和我们的请求数 -n 1000 一致

接下来我们先读取vote值然后再调用update语句进行加1操作:

$res=mysql_query('SELECT vote FROM table1 WHERE id=1',$con);
$res=mysql_fetch_array($res);
mysql_query('update table1 set vote='.$res['vote'].'+1 where id=1',$con);

用ab模拟并发后查看数据:

QQ20170219-140316.png

结果很明显,使用这种方式在并发下数据的不一致性就发生了。

解决这个问题的一种方法是使用 事务 + InnoDB的行锁, InnoDB的行锁有两种方式: 读共享锁写独占锁

读共享锁是通过下面这样的SQL获得的:

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

如果事务A获得了先获得了读共享锁,那么事务B之后仍然可以读取加了读共享锁的行数据,但必须等事务A commit或者roll back之后才可以更新或者删除加了读共享锁的行数据。

写独占锁是通过SELECT...FOR UPDATE获得:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

如果事务A先获得了某行的写独占锁,那么事务B就必须等待事务A commit或者roll back之后才可以访问行数据。

摘自: https://my.oschina.net/feichexia/blog/263407

这里说明一下,MyISAM只支持表锁,而InnoDB支持表锁和行锁。

所以要解决这个问题,我们使用写独占锁,代码如下:

mysql_query("SET AUTOCOMMIT=0",$con);
mysql_query('BEGIN',$con);

$res=mysql_query('SELECT vote FROM table1 WHERE id=1 FOR UPDATE',$con);
$res=mysql_fetch_array($res);
mysql_query('UPDATE table1 SET vote="'.($res['vote']+1).'" WHERE id=1',$con);

mysql_query('COMMIT',$con);

并发后结果为1000,说明正确:

QQ20170219-140316.png

当然,也可以使用表锁:

mysql_query("SET AUTOCOMMIT=0",$con);
mysql_query('BEGIN',$con);

mysql_query('LOCK TABLES table1 WRITE',$con);

$res=mysql_query('SELECT vote FROM table1 WHERE id=1',$con);
$res=mysql_fetch_array($res);
mysql_query('UPDATE table1 SET vote="'.($res['vote']+1).'" WHERE id=1',$con);

mysql_query('UNLOCK TABLES',$con);

mysql_query('COMMIT',$con);

其他参考:

Innodb中的事务隔离级别和锁的关系
数据库事务、隔离级别和锁
你对Redis的使用靠谱吗?

标签: mysql