一.问题的背景
-
前段时间,我们作了一个项目,让客户使用了一段时间后发现一些问题,我们打开客户的数据库看了看,发现存在很多重复数据,重复数据从何而来,我看了看我们的程序,好像我们的代码已经保证了数据的唯一性。当时通过我深入的研究发现在里面存在一个大大问题。
二.问题的引入
我们程序的思路代码思路大致如下(这里为了阐述问题,只是举个简单例子):
1. 假如存在一个表student,表结构如下:
- studentid int (z主键,递增字段,MsSql 2000通过identity标识,oracle通过sequence和触发器来实现)
- name varchar(20)
- age int
- 假如此表的数据如下:
-
studentid
|
name
|
age
|
1
|
张三
|
16
|
2
|
李四
|
20
|
3
|
王五
|
23
|
...
|
...
|
...
|
-
- 2. 程序处理步骤:
- (a)外部传来一个stuid,stuname,stuage
-
(b)先根据外部传来的stuid在数据库中查询此stuid对应的记录行,如果数据库里没有此条件的记录,则把 (stuid,stuname,stuage)插入数据库;如果数据库里有此纪录行,则把此记录行的studentname和age列的数据改为stuname,stuage;程序代码如下:
public class Student{
public static void insert(String stuname,String stuage)
{
try{
DataBase db=new DataBase();//对数据库连接,查询进行了封装
db.conectDb();
String sql="select * from student where name='"+stuname+"' ";
ResultSet rs=db.query(sql);
boolean b=false;
String stuid="";
while(rs.next){
stuid=rs.getString("studentid");
b=true;
break;
}
if(b)//如果表里没有stuname,则插入一条新纪录
{
sql="insert into student (name,age)values("'"+stuname+"'","+age+")";//student表里的studentid是自动产生的,oracle里用sequence实现
}else{//如果找着了stuid对应的行,则更新
sql=”update student set name='"+stuname+"'",age="+age+" where studentid="+stuid;
}
db.update(sql);
db.close();
}catch(Exception e)
{
db.close();
e.printStackTrace();
}
}
代码片段1-1
- (3)servlet处理
-
当然,程序是通过servlet来调用Student.insert(stuid,stuname,stuage)来处理用户的请求的,servlet代码如下:
public class InsertServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String stuname=request.getParameter(“stuname”);
String stuage=request.getParameter(“stuage”);
Student.insert(stuname,stuage);
}
}
三.问题分析
从上面代码片段1-1不仔细看,还真觉得没什么问题,程序好像也保证了向数据库插入或更新数据的唯一性。哈哈,这只是表面现象,因为事实是数据库里存在重复数据是千真万确的。如果你不信,现在来测试一下。我们用多线程来测试,每个线程就相当于一个客户端。线程如下:
public class Test extends Thread{
public Test(){
start();
}
public void run(){
Student.insert("小龙", 20+"");
}
public static void main(String[] args){
for(int i=0; i<1000; i++){
new Test();
}
}
}
从上面的代码可以看出,我要向数据库里插入studentname='小龙' and studentage=20的数据,但先要检查是否存在studentid=3的记录,如果没有才插入。通过此线程我们会发现数据库里有时候会出现两条关于name为='小龙'并且age=20的记录。 有时候运行此测试代码一段时间后数据里依然没有重复数据,你可以把此代码同时放到多个机器,如果是双核cpu的话出现重复的机率可能会大一些。
大概你已经知道问题所在,问题就在于并发。因为有很多用户可能同时在向数据库发送请求。
public class Student{
public static void insert(String stuname,String stuage)
{
try{
DataBase db=new DataBase();//对数据库连接,查询进行了封装
db.conectDb();
String sql="select * from student where name='"+stuname+"' ";
ResultSet rs=db.query(sql);
boolean b=false;
String stuid="";
/*假设有两个线程同时运行到此处,假设这两个线程都查找student表里name为"小龙"的记录,此时他们都会发现student表里没有记录,所以他们都会向student表里插入“小龙”的记录,这就造成了重复记录。*/
while(rs.next){
stuid=rs.getString("studentid");
b=true;
break;
}
if(b)//如果表里没有stuname,则插入一条新纪录
{
sql="insert into student (name,age)values("'"+stuname+"'","+age+")";//student表里的studentid是自动产生的,oracle里用sequence实现
}else{//如果找着了stuid对应的行,则更新
sql=”update student set name='"+stuname+"'",age="+age+" where studentid="+stuid;
}
db.update(sql);
db.close();
}catch(Exception e)
{
db.close();
e.printStackTrace();
}
}
上述红体分析是产生数据库出现重复的原因,也是程序员容易犯的一个错误,最简单的解决办法是加上唯一性约束条件, 假设表student的name是唯一的,那我们就给name加唯一性约束unique。所以数据库会保证只有一个唯一确定的name,当两个请求同时向数据库插入相同的name时,会采用抢占式插入,谁先插入其他方就不能再插入数据。
上述方法解决了数据库里出现重复性数据问题。但还可以用其他的方法解决,这就涉及到数据库的事务的并发控制。下次再讨论。