1. 多线程最好不要共用一个连接
以下是来自MSDN的一段话http://msdn.microsoft.com/zh-cn/library/ms131686.aspx
“SQL Server 2005 在访问数据库引擎的应用程序中引入了对多个活动结果集 (MARS) 的支持。 在 SQL Server 的早期版本中,数据库应用程序无法在单个连接上保持多个活动语句。 使用 SQL Server 默认结果集时,应用程序必须先处理或取消从某一批处理生成的所有结果集,然后才能对该连接执行任何其他批处理。 SQL Server 2005 引入了新的连接属性,该属性允许应用程序在每个连接上使用多个待定请求,具体而言,每个连接可以具有多个活动的默认结果集。”
经过在SQL2000下的测试发现,无论是ADO基于ODBC连接、ADO基于OLEDB直接连接还是ADO基于OLEDB(OLEDB基于ODBC)连接测试都会出现问题,报错为“[Microsoft][ODBC SQL Server Driver]连接占线导致另一个 hstmt”,只是错误出现的时间不一定,我想这与CPU对线程的调度有关,如果两个线程在调度时并没有真正的对数据库同时操作,这样是不会出问题的,需要长时间测试;这也就验证了MSDN上的这段话,不过SQL2005及以上的版本没有测试,不过在SQL2005及以上版本中多个活动结果集默认是不启用的,需要在连接字符串中写明启用才行,具体参考MSDN。不管怎样在开发最好不要多个线程共用一个数据库连接,这样会很容易出问题,最好是一个线程一个连接。
2. 什么时候需要线程同步或存储过程加锁
在下面有个例子是对数据库insert、update、select同时操作,测试结果证明不需要加锁或者线程同步,因为这里三个线程对数据库的操作都简单的原子操作,数据库在执行一个线程的数据库操作时如果加了锁(见注释),另外也要操作的线程可能就要等待,等待上个操纵完成再进行操纵,数据库自己就有锁管理机制,只会出现线程等待,也就是类似队列操作。但是如果有比较复杂的事务操作的话就需要加锁了,如果不加的话就会出现你说的脏读、死锁等问题,同时在进行事务操作时也可加超时限制,超过多长时间没有返回就认为操作失败,退出操作,这样可以在发生死锁时解除死锁,防止死等待。
注:SQLSERVER中锁有粒度和模式之分,粒度有行锁、页锁、表锁、数据库锁等,而且数据库内部有自动的锁管理器,一般情况下不需要我们手动设置,在锁的个数比较多时,锁会自动升级以节省系统资源。锁的模式可分为共享锁(一般查询操作数据库自己加)、更新锁、排他锁等,一般造成死锁都是在事务操作同时请求对方持有的被锁定的资源时陷入互相等待的原因。具体关于锁的介绍及机制,请查资料。
例子:
三个线程,每个线程起一个连接,分别更新、插入、查询
OnInitDialog添加:
m_hEvent = CreateEvent(NULL,TRUE,FALSE,_T("ThreadControl"));
//打开数据库 BOOL CTestThreadDlg::DatabaseConnect(_ConnectionPtr &pConnection) { CString strSQL; HRESULT hr; AfxOleInit();//注意每个线程都要初始化 try { hr=pConnection.CreateInstance(__uuidof(Connection)); //OLEDB-ODBC //strSQL="Provider=MSDASQL;driver={SQL Server};SERVER=192.168.1.105;UID=sa;PWD=;DATABASE=TestDB;"; //OLEDB //strSQL="Provider=SQLOLEDB.1;Data Source=192.168.1.105;Initial Catalog=TestDB;User ID=sa;PWD=;"; //ODBC strSQL="driver={SQL Server};SERVER=192.168.1.105;UID=sa;PWD=;DATABASE=TestDB;"; if(SUCCEEDED(hr)) { hr=pConnection->Open(_bstr_t(strSQL),"","",adModeUnknown); } } catch(_com_error &e) { ComError("连接数据库\n",e); AfxMessageBox("连接失败"); return FALSE; } return TRUE; } //线程一:更新线程 UINT CTestThreadDlg::Thread1(LPVOID lpParam) { CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam; if (!pCurDlg->DatabaseConnect(pCurDlg->pConnectionPtr1)) return 0; while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0) { try { UINT t1=GetTickCount(); CString strSql = "update Table_1 set age=1 where name='0000000000'"; COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); _ConnectionPtr pConnectionPtr = pCurDlg->pConnectionPtr1; pConnectionPtr->Execute(_bstr_t(strSql),vtOptional,-1); pCurDlg->m_iCount1++; UINT t2=GetTickCount(); CString sText; sText.Format("线程一用时:%d\n",t2-t1); OutputDebugString(sText); } catch (_com_error &e) { pCurDlg->ComError("线程一",e); } } pCurDlg->pConnectionPtr1->Close(); pCurDlg->pConnectionPtr1.Release(); return 0; } //线程二:插入线程 UINT CTestThreadDlg::Thread2(LPVOID lpParam) { CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam; if (!pCurDlg->DatabaseConnect(pCurDlg->pConnectionPtr2)) return 0; while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0) { try { UINT t1=GetTickCount(); CString strSql = "insert into Table_1 values('0000000000',0)"; COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); _ConnectionPtr pConnection = pCurDlg->pConnectionPtr2; pConnection->Execute(_bstr_t(strSql),vtOptional,-1); pCurDlg->m_iCount2++; UINT t2=GetTickCount(); CString sText; sText.Format("线程二用时:%d\n",t2-t1); OutputDebugString(sText); } catch (_com_error &e) { pCurDlg->ComError("线程二",e); } } return 0; } //线程三:查询线程 UINT CTestThreadDlg::Thread3(LPVOID lpParam) { CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam; if (!pCurDlg->DatabaseConnect(pCurDlg->pConnectionPtr3)) return 0; while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0) { try { UINT t1=GetTickCount(); _CommandPtr pCommandPtr; pCommandPtr.CreateInstance("ADODB.Command"); pCommandPtr->ActiveConnection = pCurDlg->pConnectionPtr3; pCommandPtr->CommandText ="select * from Table_1"; _RecordsetPtr pRecordsetPtr = pCommandPtr->Execute(NULL,NULL,adCmdText); _variant_t vName = pRecordsetPtr->GetCollect((_variant_t)(long)0); CString sName = (LPCTSTR)_bstr_t(vName); pCommandPtr.Release(); pRecordsetPtr->Close(); pRecordsetPtr.Release(); pCurDlg->m_iCount3++; UINT t2=GetTickCount(); CString sText; sText.Format("线程三用时:%d\n",t2-t1); OutputDebugString(sText); } catch (_com_error &e) { pCurDlg->ComError("线程三",e); } } pCurDlg->pConnectionPtr2->Close(); pCurDlg->pConnectionPtr2.Release(); return 0; } //点击开始 void CTestThreadDlg::OnButton2() { // TODO: Add your control notification handler code here ResetEvent(m_hEvent); m_iCount1=m_iCount2=m_iCount3=0; AfxBeginThread((AFX_THREADPROC)Thread1, reinterpret_cast<LPVOID>(this)); AfxBeginThread((AFX_THREADPROC)Thread2, reinterpret_cast<LPVOID>(this)); AfxBeginThread((AFX_THREADPROC)Thread3, reinterpret_cast<LPVOID>(this)); } //点击停止 void CTestThreadDlg::OnButton3() { // TODO: Add your control notification handler code here SetEvent(m_hEvent); CString sText; sText.Format("线程一更新%d\r\n线程二插入%d\r\n线程三查询%d\r\n",m_iCount1,m_iCount2,m_iCount3); AfxMessageBox(sText); } void CTestThreadDlg::ComError(CString sThread,_com_error e) { CString ErrorStr; _bstr_t bstrSource(e.Source()); _bstr_t bstrDescription(e.Description()); ErrorStr.Format( "%sError Code = %08lx Code meaning = %s Source = %s Description = %s\n", sThread,e.Error(), e.ErrorMessage(), (LPCSTR)bstrSource, (LPCSTR)bstrDescription ); OutputDebugString(ErrorStr); }