一.問題的背景
-
前段時間,我們作了一個項目,讓客戶使用了一段時間后發(fā)現(xiàn)一些問題,我們打開客戶的數(shù)據(jù)庫看了看,發(fā)現(xiàn)存在很多重復(fù)數(shù)據(jù),重復(fù)數(shù)據(jù)從何而來,我看了看我們的程序,好像我們的代碼已經(jīng)保證了數(shù)據(jù)的唯一性。當(dāng)時通過我深入的研究發(fā)現(xiàn)在里面存在一個大大問題。
二.問題的引入
我們程序的思路代碼思路大致如下(這里為了闡述問題,只是舉個簡單例子):
1. 假如存在一個表student,表結(jié)構(gòu)如下:
- studentid int (z主鍵,遞增字段,MsSql 2000通過identity標識,oracle通過sequence和觸發(fā)器來實現(xiàn))
- name varchar(20)
- age int
- 假如此表的數(shù)據(jù)如下:
studentid
name
age
1
張三
16
2
李四
20
3
王五
23
...
...
...
- 2. 程序處理步驟:
- (a)外部傳來一個stuid,stuname,stuage
(b)先根據(jù)外部傳來的stuid在數(shù)據(jù)庫中查詢此stuid對應(yīng)的記錄行,如果數(shù)據(jù)庫里沒有此條件的記錄,則把 (stuid,stuname,stuage)插入數(shù)據(jù)庫;如果數(shù)據(jù)庫里有此紀錄行,則把此記錄行的studentname和age列的數(shù)據(jù)改為stuname,stuage;程序代碼如下:
public class Student{
public static void insert(String stuname,String stuage)
{
try{
DataBase db=new DataBase();//對數(shù)據(jù)庫連接,查詢進行了封裝
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是自動產(chǎn)生的,oracle里用sequence實現(xiàn)
}else{//如果找著了stuid對應(yīng)的行,則更新
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處理
當(dāng)然,程序是通過servlet來調(diào)用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不仔細看,還真覺得沒什么問題,程序好像也保證了向數(shù)據(jù)庫插入或更新數(shù)據(jù)的唯一性。哈哈,這只是表面現(xiàn)象,因為事實是數(shù)據(jù)庫里存在重復(fù)數(shù)據(jù)是千真萬確的。如果你不信,現(xiàn)在來測試一下。我們用多線程來測試,每個線程就相當(dāng)于一個客戶端。線程如下:
三.問題分析
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();
}
}
} - name varchar(20)
大概你已經(jīng)知道問題所在,問題就在于并發(fā)。因為有很多用戶可能同時在向數(shù)據(jù)庫發(fā)送請求。
public class Student{
public static void insert(String stuname,String stuage)
{
try{
DataBase db=new DataBase();//對數(shù)據(jù)庫連接,查詢進行了封裝
db.conectDb();
String sql="select * from student where name='"+stuname+"' ";
ResultSet rs=db.query(sql);
boolean b=false;
String stuid="";
/*假設(shè)有兩個線程同時運行到此處,假設(shè)這兩個線程都查找student表里name為"小龍"的記錄,此時他們都會發(fā)現(xiàn)student表里沒有記錄,所以他們都會向student表里插入“小龍”的記錄,這就造成了重復(fù)記錄。*/
while(rs.next){
stuid=rs.getString("studentid");
b=true;
break;
}
if(b)//如果表里沒有stuname,則插入一條新紀錄
{
sql="insert into student (name,age)values("'"+stuname+"'","+age+")";//student表里的studentid是自動產(chǎn)生的,oracle里用sequence實現(xiàn)
}else{//如果找著了stuid對應(yīng)的行,則更新
sql=”update student set name='"+stuname+"'",age="+age+" where studentid="+stuid;
}
db.update(sql);
db.close();
}catch(Exception e)
{
db.close();
e.printStackTrace();
}
}
上述紅體分析是產(chǎn)生數(shù)據(jù)庫出現(xiàn)重復(fù)的原因,也是程序員容易犯的一個錯誤,最簡單的解決辦法是加上唯一性約束條件, 假設(shè)表student的name是唯一的,那我們就給name加唯一性約束unique。所以數(shù)據(jù)庫會保證只有一個唯一確定的name,當(dāng)兩個請求同時向數(shù)據(jù)庫插入相同的name時,會采用搶占式插入,誰先插入其他方就不能再插入數(shù)據(jù)。
上述方法解決了數(shù)據(jù)庫里出現(xiàn)重復(fù)性數(shù)據(jù)問題。但還可以用其他的方法解決,這就涉及到數(shù)據(jù)庫的事務(wù)的并發(fā)控制。下次再討論。