高并发 之 表处理
在以前的项目中,经常遇见一些高并发因而时常会出现重复数据。从而对我们操作的业务产生了一定的影响。
现象
由于遗留的历史原因系统对高并发应对很脆弱很无力,因而常常会出现某一个表中重复插入数据。
原因
由于网络原因,客户可能连续推送了两条重复的数据,两条数据时间间隔非常小,因此导致了我们的
```
if(用户不存在)
{
xxxxx
存储用户到数据库
}
else
{
重复推送,不采取任何措施
}
```
这个操作还没有执行完毕,第二条拥有相同数据的线程已经进入并通过了if的检验,导致数据库存储了两条相同的数据。后来我自己写了个100并发的多线程测试程序,发现100条相同数据中有40条被插入到了数据库里!天啦噜!!!因此确定了是多线程的并发导致了程序的判断逻辑失效。
解决思路
在表中对 身份证号 添加了唯一索引,以限制重复插入数据。
CREATE TABLE `t_lockUser` ( `id` bigint(10) unsigned NOT NULL AUTO_INCREMENT, `userId` bigint(20) NOT NULL, `recordTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT , `pkTime` date NOT NULL COMMENT '联合主键用的时间', `status` int(1) NOT NULL DEFAULT '1' COMMENT , PRIMARY KEY (`id`), UNIQUE KEY `pk_userId_investFirstTime_only` (`pkTime`,`userId`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC ;
这样使用 pkTime和userId作为唯一索引,就能保证每个用户每天只能往该表中插入一条数据。
此外需要注意一个问题那就是倘若索引过大,消耗的资源同时也会很大。
考虑使用synchronized对方法添加同步锁,但是这样会导致其他正常数据的推送线程也被阻塞,影响效率。因此不采用。
使用对数据库添加行锁,实验发现还是会出现2条重复数据
分析: 理论上的结果应该是1条成功,149条失败。对数据库的select语句添加行锁必须作用于某条记录,但是第一次报送时,数据库中并没有这条数据,因此行锁根本没有加上,导致第二条数据成功异步使用select语句。第一次报送成功以后,数据库中有了这条数据,select语句成功的对这条记录添加了行锁,所以后边不会出现重复数据。因此此法不可用。即想提高效率不对方法添加synchronized,又想保证数据准确性,最后使用synchronized(siteId + uid) 在Controller层加锁(保证了只有重复数据被加锁,在Controller使用的原因是因为事务会在Service调用完毕才被提交,我实验过在Service同步,150并发会出现2条重复数据,因为事务还没来得及提交)
测试结果:测试了3次150并发 不到一秒的时间全部返回,结果1条登记成功,149条返回该作者已登记。
优化点
这种加同步锁的方法在负载均衡下的多台应用服务器会失效!因为就算Spring保证了对象是单例的,但是多台服务器肯定是多个对象!因此synchronized将无效。解决方法是在数据库层对该对接公司的唯一记录加select锁,这样就能保证数据的不重复性,但是会降低该公司推送数据的效率(相当于逐条推送),但是公司与公司之间还是并行推送的。还有一个方法就是将业务逻辑写入存储过程,然后对存储过程加锁,这种方法太麻烦了,需求有变动就必须去修改存储过程,但是效率要比前者高得多。
参考:http://m.blog.csdn.net/nikobelic8/article/details/53308543