文档始创于2010-11-18
版权 © 2011, 2012, 2013 Netkiller(Neo Chan). All rights reserved.
版权声明
转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。
|
|
$Date: 2013-04-10 15:03:49 +0800 (Wed, 10 Apr 2013) $
我的系列文档
Netkiller Architect 手札 | Netkiller Developer 手札 | Netkiller PHP 手札 | Netkiller Python 手札 | Netkiller Testing 手札 | Netkiller Cryptography 手札 |
Netkiller Linux 手札 | Netkiller CentOS 手札 | Netkiller FreeBSD 手札 | Netkiller Security 手札 | Netkiller Version 手札 | Netkiller Web 手札 |
Netkiller Monitoring 手札 | Netkiller Storage 手札 | Netkiller Mail 手札 | Netkiller Shell 手札 | Netkiller Network 手札 | Netkiller Database 手札 |
Netkiller PostgreSQL 手札 | Netkiller MySQL 手札 | Netkiller NoSQL 手札 | Netkiller LDAP 手札 | Netkiller Cisco IOS 手札 | Netkiller H3C 手札 |
Netkiller Multimedia 手札 | Netkiller Docbook 手札 | Netkiller 开源软件 手札 |
CREATE TABLE `account` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `user` VARCHAR(50) NOT NULL DEFAULT '0', `cash` FLOAT NOT NULL DEFAULT '0', `point` INT(10) UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE INDEX `user` (`user`) ) COLLATE='utf8_general_ci' ENGINE=InnoDB;
INSERT INTO `test`.`account` (`user`, `cash`,`point`) VALUES ('neo', 10,10);
下面通过account表,我来模拟一个返点场景,例如电商网站经常会用到“返点”,购买一定数量的商品赠送一定的点数,可以通过点数买东西,这样涉及到点的加于减操作。
表 8.1. 更新丢失演示
select point into @point from account where user='neo'; |
|
select point into @point from account where user='neo'; |
|
update account set point=@point+20 where user='neo'; |
|
update account set point=@point+50 where user='neo'; |
看看最后用户有多少点?
mysql> select point from account where user='neo'; +-------+ | point | +-------+ | 30 | +-------+ 1 row in set (0.00 sec)
傻了吧,老板发火,测试不能重现,运维说这是程序计算错误,程序员说程序没有错误,这样的场景国内很多公司都出现过吧?
问题出在哪里呢?出在并发上,很多web程序员很少考虑并发是产生的问题,怎么解决?很多方案,在我的职业生涯过程就见过很多奇葩方案,都能解决问题但不太完美。
如果更新语句改为 update account set point=@point+50 where user='neo' and point=@point; 会更保险,但仍然不能解决同意时间所产生的更新操作
下面是通过事务与锁彻底解决上面的问题。
表 8.2. 防止更新丢失加锁演示
begin; select point into @point from account where user='neo' for update; |
|
begin; select point into @point from account where user='neo' for update; 执行到此处会挂起 |
|
update account set point=@point+20 where user='neo'; commit; |
|
update account set point=@point+50 where user='neo'; commit; |
上面解决更新覆盖问题,但从数据库设计角度是不应该这样设计表的。仅供参考
CREATE TABLE `account` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `user` VARCHAR(50) NOT NULL DEFAULT '0', `cash` FLOAT NOT NULL DEFAULT '0', `point` INT(10) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) COLLATE='utf8_general_ci' ENGINE=InnoDB;
每一次数据变化新增一条数据
INSERT INTO `test`.`account` (`user`, `point`) VALUES ('neo', -10); INSERT INTO `test`.`account` (`user`, `point`) VALUES ('neo', -5); INSERT INTO `test`.`account` (`user`, `point`) VALUES ('neo', 30); INSERT INTO `test`.`account` (`user`, `point`) VALUES ('neo', -20);
计算剩余点数
select sum(point) as point from account where user='neo';