数据库同时更新问题

Netkiller MySQL 手札

MySQL MariaDB...

MrNeo Chan陈景峰(BG7NYT)


中国广东省深圳市龙华新区民治街道溪山美地
518131
+86 13113668890
+86 755 29812080

文档始创于2010-11-18

版权 © 2011, 2012, 2013 Netkiller(Neo Chan). All rights reserved.

版权声明

转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。

文档出处:
http://netkiller.github.io
http://netkiller.sourceforge.net

 

$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 开源软件 手札      

 

 

8.2.3. 解决更新冲突

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. 更新丢失演示

Session A Session B
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. 防止更新丢失加锁演示

Session A Session B
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';

你可能感兴趣的:(数据库同时更新问题)