多线程操作SQLSERVER数据库的问题

wanglx2012 2014-02-22 11:37:51
VC通过_ConnectionPtr链接数据库,建立一个全局链接,然后起五个线程,三个线程插入同一张表a,第四个线程查询这个表a,第五个线程更新这个表a,运行没有问题,这是他们操作的次数不同,我想问几个问题
1.多个线程同时操作一张表为什么不会造成数据库的死锁,不是说多线程操作要加锁限制吗?
2.操作数据库时什么情况下会造成死锁
3.像这个同时多个线程对数据库进行insert、delete、update、select的情况在大家工作中应该经常遇到,大家一般都是怎么做的?
我的测试相关代码如下:
就是建立一个简单的对话框程序,在OnInitDialog中打开连接代码如下

//OnInitDialog中的连接代码
CString strSQL;
HRESULT hr;
try
{
hr=m_pConnection.CreateInstance(__uuidof(Connection));
strSQL="Provider=SQLOLEDB;Persist Security Info=False;Integrated Security=SSPI;Initial Catalog=MyTest;Data Source=YOS-0222140854";
if(SUCCEEDED(hr))
{
hr=m_pConnection->Open(_bstr_t(strSQL),"","",adModeUnknown);
}
}
catch(_com_error e) //捕捉异常
{
CString errormessage;
errormessage.Format(_T("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()));
AfxMessageBox(errormessage);//显示错误信息

return;
}
AfxMessageBox("连接成功");


点击开始按钮启动五个线程相关代码如下:
UINT CTestThreadDlg::Thread1(LPVOID lpParam)
{
CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam;
while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0)
{
try
{
CString strSql = "insert into Table_1 values('000000000',1)";
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
_ConnectionPtr pConnection = pCurDlg->m_pConnection;
pConnection->Execute(_bstr_t(strSql),vtOptional,-1);
pCurDlg->m_iCount1++;
OutputDebugString("线程一操作成功\n");
}
catch(_com_error* e)
{
CString errormessage;
errormessage.Format("***********************线程一数据库使用失败:%s\n", e->ErrorMessage());
OutputDebugString(errormessage);
}
}
return 0;
}
UINT CTestThreadDlg::Thread2(LPVOID lpParam)
{
CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam;
while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0)
{
try
{
CString strSql = "insert into Table_1 values('1111111111',2)";
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
_ConnectionPtr pConnection = pCurDlg->m_pConnection;
pConnection->Execute(_bstr_t(strSql),vtOptional,-1);
pCurDlg->m_iCount2++;
OutputDebugString("线程二操作成功\n");
}
catch (_com_error* e)
{
CString errormessage;
errormessage.Format("***********************线程二数据库使用失败:%s\n", e->ErrorMessage());
OutputDebugString(errormessage);
}
}
return 0;
}

UINT CTestThreadDlg::Thread3(LPVOID lpParam)
{
CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam;
while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0)
{
try
{
CString strSql = "insert into Table_1 values('2222222222',3)";
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
_ConnectionPtr pConnection = pCurDlg->m_pConnection;
pConnection->Execute(_bstr_t(strSql),vtOptional,-1);
pCurDlg->m_iCount3++;
OutputDebugString("线程三操作成功\n");
}
catch (_com_error* e)
{
CString errormessage;
errormessage.Format("***********************线程三数据库使用失败:%s\n", e->ErrorMessage());
OutputDebugString(errormessage);
}

}
return 0;
}

UINT CTestThreadDlg::Thread4(LPVOID lpParam)
{
CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam;
while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0)
{
try
{
_CommandPtr pCommandPtr;
pCommandPtr.CreateInstance("ADODB.Command");
_ConnectionPtr pConnectionPtr = pCurDlg->m_pConnection;
pCommandPtr->ActiveConnection = pConnectionPtr;
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();
CString sText;
sText.Format("查询结果:%s",sName);
OutputDebugString(sText);
OutputDebugString("\n");
pCurDlg->m_iCount4++;
OutputDebugString("线程四操作成功\n");
}
catch (_com_error* e)
{
CString errormessage;
errormessage.Format("***********************线程四数据库使用失败:%s\n", e->ErrorMessage());
OutputDebugString(errormessage);
}
}
return 0;
}
UINT CTestThreadDlg::Thread5(LPVOID lpParam)
{
CTestThreadDlg *pCurDlg = (CTestThreadDlg *)lpParam;
while (WaitForSingleObject(pCurDlg->m_hEvent,0)!=WAIT_OBJECT_0)
{
try
{
CString strSql = "update Table_1 set age=100 where name='0000000000'";
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
_ConnectionPtr pConnection = pCurDlg->m_pConnection;
pConnection->Execute(_bstr_t(strSql),vtOptional,-1);
pCurDlg->m_iCount5++;
OutputDebugString("线程五操作成功\n");
}
catch (_com_error* e)
{
CString errormessage;
errormessage.Format("***********************线程五数据库使用失败:%s\n", e->ErrorMessage());
OutputDebugString(errormessage);
}

}
return 0;
}
void CTestThreadDlg::OnButton1()
{
// TODO: Add your control notification handler code here
OnButton1();
ResetEvent(m_hEvent);
m_iCount1=m_iCount2=m_iCount3=m_iCount4=m_iCount5=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));
AfxBeginThread((AFX_THREADPROC)Thread4, reinterpret_cast<LPVOID>(this));
AfxBeginThread((AFX_THREADPROC)Thread5, 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线程四查询%d\r\n线程五更新%d",m_iCount1,m_iCount2,m_iCount3,m_iCount4,m_iCount5);
AfxMessageBox(sText);
m_pConnection->Release();
m_pConnection.Release();
}


现在运行正常,运行10分钟都正常,没有长时间测试,请大牛解释
...全文
1832 22 打赏 收藏 举报
写回复
22 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
owetointernet 2014-06-28
连接池,线程池!
  • 打赏
  • 举报
回复
manmed 2014-03-09
以前写的服务器程序老是崩溃,现在已经锁定是多线程操作数据库的问题,我采用的就是多个线程共享一个连接,查了好多天…… 可是我想问一个线程对应一个连接确实可以解决上述问题,如果连接多的话怎么办,假如一千个以上该怎么办?
  • 打赏
  • 举报
回复
wanglx2012 2014-02-27
引用 17 楼 wanglx2012 的回复:
问题越来越多了,真的很难理解: SQL Server 2005 在访问数据库引擎的应用程序中引入了对多个活动结果集 (MARS) 的支持。 在 SQL Server 的早期版本中,数据库应用程序无法在单个连接上保持多个活动语句。 使用 SQL Server 默认结果集时,应用程序必须先处理或取消从某一批处理生成的所有结果集,然后才能对该连接执行任何其他批处理。 SQL Server 2005 引入了新的连接属性,该属性允许应用程序在每个连接上使用多个待定请求,具体而言,每个连接可以具有多个活动的默认结果集。 上面的红色字体说了,一个连接在同一时间只能有一个活动请求,可是我的这个小例子为什么可以啊,我用的就是SQL2000啊,在这个小例子中用的连接方式是ADO基于OLEDB直接连接,可是假如我换成ADO基于ODBC连接或者ADO基于OLEDB(OLEDB又基于ODBC)连接的话就会很快崩溃并且报如下错误:[Microsoft][ODBC SQL Server Driver]连接占线导致另一个 hstmt,按照上面的那段话这个报错应该是正常的,因为SQL2000不支持MARS,可是换种连接方式(就是我例子中用的连接方式)为什么就可以??? 我知道在开发中最好一个线程一个连接,我只是想知道出错的原因!!
好吧,经过测试发现用ADO基于OLEDB直接连接也是会出现问题的,只是时间的长短问题而已,时间不定,不过肯定会出问题,之所以会出现时间不定的情况,应该是CPU调度线程的原因,没让他们很快出现同时操作数据库的情况,最终出现的错误也是[Microsoft][ODBC SQL Server Driver]连接占线导致另一个 hstmt,所以这就证明了上面的多个活动结果集的情况,我是在SQL2000下测试的,至于更高版本没有测试,反正不管怎么说在开发中最好不要多个线程共享一个连接,这样很容易出现问题,还是一个线程对应一个连接的好,嘿嘿,明白了
  • 打赏
  • 举报
回复
action爱生活 2014-02-26
楼主探索精神不错,通过你这篇帖子,我学到了很多!!
  • 打赏
  • 举报
回复
wanglx2012 2014-02-26
大牛啊,来吧
  • 打赏
  • 举报
回复
wanglx2012 2014-02-26
问题越来越多了,真的很难理解: SQL Server 2005 在访问数据库引擎的应用程序中引入了对多个活动结果集 (MARS) 的支持。 在 SQL Server 的早期版本中,数据库应用程序无法在单个连接上保持多个活动语句。 使用 SQL Server 默认结果集时,应用程序必须先处理或取消从某一批处理生成的所有结果集,然后才能对该连接执行任何其他批处理。 SQL Server 2005 引入了新的连接属性,该属性允许应用程序在每个连接上使用多个待定请求,具体而言,每个连接可以具有多个活动的默认结果集。 上面的红色字体说了,一个连接在同一时间只能有一个活动请求,可是我的这个小例子为什么可以啊,我用的就是SQL2000啊,在这个小例子中用的连接方式是ADO基于OLEDB直接连接,可是假如我换成ADO基于ODBC连接或者ADO基于OLEDB(OLEDB又基于ODBC)连接的话就会很快崩溃并且报如下错误:[Microsoft][ODBC SQL Server Driver]连接占线导致另一个 hstmt,按照上面的那段话这个报错应该是正常的,因为SQL2000不支持MARS,可是换种连接方式(就是我例子中用的连接方式)为什么就可以??? 我知道在开发中最好一个线程一个连接,我只是想知道出错的原因!!
  • 打赏
  • 举报
回复
赵4老师 2014-02-25
引用 2 楼 zyq5945 的回复:
长时间运行可能会出现莫名其妙的问题,最好是一个线程对应一个连接。
英雄所见略同。
  • 打赏
  • 举报
回复
wanglx2012 2014-02-25
还有个问题就是我的这个测试程序用的是OLEDB连接方式,这种连接方式跑起来没问题,可是如果我换成ODBC连接方式的话立马就蹦掉了,怎么回事,难道他们两种连接方式的管理有区别???
  • 打赏
  • 举报
回复
赵4老师 2014-02-25
搜“连接池” 搜“线程池”
  • 打赏
  • 举报
回复
wanglx2012 2014-02-25
引用 13 楼 zhao4zhong1 的回复:
[quote=引用 2 楼 zyq5945 的回复:] 长时间运行可能会出现莫名其妙的问题,最好是一个线程对应一个连接。
英雄所见略同。[/quote] 那如果很多线程的话那岂不是就会有很多连接,数据库有最大连接数的限制吧,例如B/S结构的程序,如果采用这种方法的话,如果并发性很高,那岂不是会有很多很多的连接...当然可以用连接池,不过在并发性不是很高的程序中采用一个线程对应一个连接的方法是可以的,我也是想这么做,可是多线程共用一个连接的话感觉上不是也行的吗?起码我的这个测试小程序中是没出现问题,不理解为什么会出现莫名其妙的问题!!???
  • 打赏
  • 举报
回复
wanglx2012 2014-02-24
还是搞不明白,按道理讲,我多个线程同时操作一样表进行增删改查,应该会造成死锁才对,可是事实证明并没有造成死锁,只是按照串行的方式进行了执行,难道数据库引擎自己对多个同时操作的请求进行了管理(两个请求:一个插入,一个更新,难道数据库自己将他门排成队列按顺序执行?)?…………不知道有没有相关的文档或者原理。我知道一般大家会在数据库操作上加锁来让线程同步对数据库操作,可是这个锁一定要加吗?
  • 打赏
  • 举报
回复
wanglx2012 2014-02-24
引用 11 楼 zyq5945 的回复:
多个同时操作可以看成是一个队列(数据库保证每个成功提交的数据库操作都在服务器上执行,能否顺利执行成功得看数据库记录和所要做的操作是否允许),但你不做同步的话可能会有下面的情况发生 1.读取到的数据是中间每个操作的中间数据,所谓的脏读 2.更新了不存在的记录(你要更新一个记录,但前面一个操作已经删除掉了) 3.插入已存在的记录,有主键冲突的话会发生有错误产生。
谢谢回答,我已经明白了,我上面的那个例子之所以在不加锁的情况下不出现死锁,脏读等问题,是因为我对数据库的操作都是原子操作,简单insert等,数据库自己就有锁管理机制,所以没问题,只会出现线程等待,也就是类似队列操作,如果有比较复杂的事务操作的话就需要加锁了,如果不加的话就会出现你说的脏读、死锁等问题。
  • 打赏
  • 举报
回复
ok1234567 2014-02-24
数据库本身有默认锁 只有在你觉得默认锁的级别不够时,才有必要放置更高级别的锁(事务等多查询语句操作且需要同步)
  • 打赏
  • 举报
回复
worldy 2014-02-24
数据库驱动本身已经为多线程操作做了同步,因此,数据库用户一般可以不考虑数据操作同步的问题
  • 打赏
  • 举报
回复
zyq5945 2014-02-24
多个同时操作可以看成是一个队列(数据库保证每个成功提交的数据库操作都在服务器上执行,能否顺利执行成功得看数据库记录和所要做的操作是否允许),但你不做同步的话可能会有下面的情况发生 1.读取到的数据是中间每个操作的中间数据,所谓的脏读 2.更新了不存在的记录(你要更新一个记录,但前面一个操作已经删除掉了) 3.插入已存在的记录,有主键冲突的话会发生有错误产生。
  • 打赏
  • 举报
回复
zyq5945 2014-02-23
数据库有数据库的锁,和事务级别关联的,你搜下“SQL SERVER 事务”,多个数据库连接的话都是靠事务来保证数据的一致性。 如果只是一个程序的话,程序中可以共用一个连接,但在一个时间片内用程序锁来保证只有一个线程使用这个连接。 数据库的插入,修改,和删除都很快,查询只要不是一下子返回很多记录的话也很快。
  • 打赏
  • 举报
回复
wanglx2012 2014-02-23
引用 5 楼 zyq5945 的回复:
没有仔细验证过,不过一般都是加锁或者一个线程用一个连接。 可以这样考虑,ADO是一个类,多线程调用了一个类实例的方法,如果类是线程安全的完全没有问题,但ADO还依赖具体数据库驱动,还得保证数据库驱动也是线程安全的才能保证完全没有问题。
谢谢您的回答,我还是不太明白 1.如果一个线程一个连接的话,那同时起多个线程操作一张表,不是还会遇到同样的问题吗?两个线程同时起两个连接同时插入一张表就没问题?数据库本身不会锁表吗? 2.如果在代码中对数据库操作进行加锁的话,可是我的代码对数据库的响应时间有时间限制,比如不能让一个线程等待超过一秒,这样的话我的业务逻辑就会判断为超时,那么该怎么办?例如线程一对表进行插入操作时,对数据库操作进行了加锁,可是这是线程二也来了也要进行插入或者查询操作,这是线程2就要等着线程1操作完成,才能继续下面的操作,这样的话会不会造成线程2长时间等待?我现在的程序是不允许这样等待的!
  • 打赏
  • 举报
回复
zyq5945 2014-02-23
没有仔细验证过,不过一般都是加锁或者一个线程用一个连接。 可以这样考虑,ADO是一个类,多线程调用了一个类实例的方法,如果类是线程安全的完全没有问题,但ADO还依赖具体数据库驱动,还得保证数据库驱动也是线程安全的才能保证完全没有问题。
  • 打赏
  • 举报
回复
wanglx2012 2014-02-23
引用 2 楼 zyq5945 的回复:
长时间运行可能会出现莫名其妙的问题,最好是一个线程对应一个连接。
能说详细点吗,不明白具体愿意
  • 打赏
  • 举报
回复
wanglx2012 2014-02-23
引用 1 楼 wanglx2012 的回复:
等待大牛回复
能说详细点吗?不明白具体原因
  • 打赏
  • 举报
回复
加载更多回复(2)
发帖
数据库

3981

社区成员

VC/MFC 数据库
社区管理员
  • 数据库
加入社区
帖子事件
创建了帖子
2014-02-22 11:37
社区公告
暂无公告