|
我有一个C#程序,需要对SQL Server表执行一组批量更新(20k
; y4 T7 ~/ K7 _' }! ^7 l( x" f* [+)。由于其他用户可以一次通过Intranet网站一次更新这些记录,因此我们需要构建具有锁定表功能的C#程序。一旦表被锁定以防止其他用户进行任何更改/搜索,我们将需要执行所请求的更新/插入。
0 a$ q; m0 y/ n# ^: Z由于我们正在处理大量记录,因此我们无法使用TransactionScope(起初似乎是最简单的方法),因为我们的交易最终由MSDTC服务处理。我们需要使用另一种方法。
, m, W# T9 i) Y& C4 y根据我在网上阅读的内容,使用SqlTransaction对象似乎是最好的方法,但是我无法锁定表。当程序运行并且我逐步浏览以下代码时,我仍然能够通过Intranet网站执行更新和搜索。* U0 s9 }; `/ Y$ i7 d
我的问题是双重的。我使用SqlTransaction得当吗?如果是这样(或什至不是),是否有更好的方法来获取表锁定,该表锁定允许正在运行的当前程序搜索并执行更新?
: g2 {! t5 Y& n6 M9 e我希望表在程序执行以下代码时被锁定。
7 ] O% C/ T# t4 \2 c' ]1 DC#0 s1 d' f( A2 C. X2 }
SqlConnection dbConnection = new SqlConnection(dbConn);: h3 z+ J4 s2 G7 L3 z+ M: F
dbConnection.Open();
. [2 s$ h7 h. Kusing (SqlTransaction transaction = dbConnection.BeginTransaction(IsolationLevel.Serializable))
9 [' }2 E/ B8 r1 S+ m{ y7 C$ b0 p! O/ Z7 i
//Instantiate validation object with zip and channel values% ?% c1 j1 t) o9 i/ f9 |# t
_allRecords = GetRecords();% V' k- u* O; y0 r% s
validation = new Validation();
2 {1 F+ t+ _. \+ U, r1 W, [8 O/ f validation.SetLists(_allRecords);) F2 n& h- |( @- S; E# e i
while (_reader.Read())
% q$ c' f3 o* h& ^8 j {
: ~6 l0 q& f# c try
! ^6 u6 w s" N; q$ L {8 E4 x; i0 f) ?% c0 K4 F* g; ^1 A: \
record = new ZipCodeTerritory();
9 ?1 D& V( a& ^2 A3 q) A# H* t* C _errorMsg = string.Empty;
+ m0 [ Q4 G4 I) } //Convert row to ZipCodeTerritory type
; Y: K1 @. Q. K3 q" f record.ChannelCode = _reader[0].ToString();
: X) A4 O# H' z4 m4 l$ S% ]8 [2 I& \ record.DrmTerrDesc = _reader[1].ToString();6 |) \# y3 c( \9 s
record.IndDistrnId = _reader[2].ToString();
}, i# {1 _7 [ record.StateCode = _reader[3].ToString().Trim();
' @& z' l9 j0 ^- P& d record.ZipCode = _reader[4].ToString().Trim();# y" _1 V- s+ d
record.LastUpdateId = _reader[7].ToString();1 @+ o% z' F9 ?, O/ Q
record.ErrorCodes = _reader[8].ToString();
2 h9 n; m, M* a3 l) @ h record.Status = _reader[9].ToString();
6 H2 G- e7 S4 E: L; h2 Y record.LastUpdateDate = DateTime.Now;
, `& j8 s9 {: S- z. S //Handle DateTime types separetly0 H/ c: j/ Y% Y" P/ v1 m
DateTime value = new DateTime();+ f3 x, u) |* o8 L' ]
if (DateTime.TryParse(_reader[5].ToString(), out value))
1 I2 n) }" q; j0 q9 ?* j! R& O7 H& V {3 `, f7 o" u# t' O, [3 I2 I
record.EndDate = Convert.ToDateTime(_reader[5].ToString());
- v! L, ?0 U- E+ y/ P* _ }
B- ^: a2 v8 ^ else
& M( S3 h5 _) S k! g2 u3 C$ n$ i {
) i+ G: y5 d7 K/ v _errorMsg += "Invalid End Date; ";1 m4 k/ V9 ^; j- s7 A6 L9 h
}3 C2 ^1 O5 \, p6 R% x3 @
if (DateTime.TryParse(_reader[6].ToString(), out value))1 m- |9 Y( w# ]$ `
{
, S6 e: Z& i2 ?9 I0 } record.EffectiveDate = Convert.ToDateTime(_reader[6].ToString());
; s1 w8 Q6 B4 j, C! K, c }' w6 X8 r) k6 u9 i
else: ~0 Q0 A- K; f
{/ q. P1 N% b; t6 o: i8 y2 G" `- |
_errorMsg += "Invalid Effective Date; ";
1 F- k$ f( f q' @7 B1 j }
' w* [. {0 P& y2 P$ f7 b' ^6 \6 e* M //Do not process if we're missing LastUpdateId$ _8 l. _4 x0 g: _6 `
if (string.IsNullOrEmpty(record.LastUpdateId))) v: G* S( F: A1 H7 l6 Y
{+ l* l; n. L, R/ {. Y3 u, i
_errorMsg += "Missing last update Id; ";
: z% y4 s1 w3 {, H } T# _* j5 {+ s" g- \) y, T* q
//Make sure primary key is valid: e0 z _& K+ X" {
if (_reader[10] != DBNull.Value)
" ~& J; P' ]& B7 s. r {
9 F: j* x. ?5 Y/ }" O int id = 0;
9 d2 `' O3 X# }; a, D- p if (int.TryParse(_reader[10].ToString(), out id)) q* F' y4 _+ I: r
{2 J) a. I) t \. X" p. a. R
record.Id = id;7 ]! l. {2 i4 ~8 B( w) r
}: W) p2 H! p$ I" Z
else ~6 S: v& W: z. \
{
2 t& W6 N6 `! X3 E _errorMsg += "Invalid Id; ";3 N2 ?3 {" @* M* _& ~
}
: s }" H6 i+ {/ W9 K m/ |) G. c }
: n# ~6 T2 b; ] //Validate business rules if data is properly formatted
* u1 _3 u. Y' f! n if (string.IsNullOrWhiteSpace(_errorMsg))$ f: V) _& [" }. S4 ~ @
{
( `- r8 z! d/ h _errorMsg = validation.ValidateZipCode(record);
& J( Z! W7 G% T8 v4 v }
0 s1 P5 f, @5 K0 | q3 M //Skip record if any errors found
" t" j9 t; M% ~; k( W if (!string.IsNullOrWhiteSpace(_errorMsg))& Y1 ]2 ?3 f$ H t7 x% X* Z7 A
{
/ k% P7 B' E* o% r2 s% P, i7 N1 c" Z% n _issues++;
: ^. L8 Y3 y6 e# w- j //Convert to ZipCodeError type in case we have data/formatting errors8 n, W5 a( e. w
_errors.Add(new ZipCodeError(_reader), _errorMsg);. {8 n: t, E; c' r# K) u3 g
continue;6 c) {* A, U" H* w
}
& P, |' \7 ]: a2 O( v0 i: c7 B8 E( ? else if (flag)
' Y: z5 F, X; D8 p/ E {4 W: B; ?! g k2 o& K0 y" b8 g
//Separate updates to appropriate list3 ]- \- q" Y+ k: l$ @ |
SendToUpdates(record);
9 n; c" M( H% d. P0 j0 F- s }8 ~" z2 ~& N& b& B4 x& x& |
}
4 I9 @( P0 O6 \: D+ q catch (Exception ex)& i$ {$ {4 q& D Q
{ o9 D) t8 L4 E# q+ d" d
_errors.Add(new ZipCodeError(_reader), "Job crashed reading this record, please review all columns.");& f6 A- `9 r# T3 g/ B- |% w
_issues++;- I8 ~/ r9 s; ]- |% b8 y
}+ z. J- l' A' D k' }4 }+ u) l- q
}//End while
2 h% F, l; K9 V0 q6 S c M
6 M( k/ @" r7 ]0 j- x //Updates occur in one of three methods below. If I step through the code,
, A( H9 }2 p( g( R, y/ J+ z) g //and stop the program here, before I enter any of the methods, and then
( i4 ^8 X6 h4 d5 l //make updates to the same records via our intranet site the changes
0 s# d+ ^' i8 {+ T //made on the site go through. No table locking has occured at this point. 1 o+ r( q. n7 @+ {
if (flag)
* l& S2 R. F9 { {
. V! w. L2 `* p if (_insertList.Count > 0)% Y' N$ r5 m9 ]; {& u; K8 A' ^; A
{
4 l& I, t, Q% V5 M6 B Updates.Insert(_insertList, _errors);" A, D/ T# x# x/ K
}
& ^/ C% r$ M; k- l9 l x9 m9 v if (_updateList.Count > 0)
$ [9 V1 f% c' H- f, E/ Z: d1 q* z0 q0 {- l6 Y {. Z( Y! q9 Z4 h9 ^8 H5 s
_updates = Updates.Update(_updateList, _errors);* |2 U. H8 N- M0 N) y4 ~
_issues += _updateList.Count - _updates;
# `/ p6 L$ v, b7 m# S6 v8 [! c5 g }' _% c7 H3 O7 B; N+ }, X
if (_autotermList.Count > 0)! q& J5 }) ^! }7 P, {
{
6 ]5 c: x! w& M5 |# c% f, ` //_autotermed = Updates.Update(_autotermList, _errors);9 c! P8 M8 ^. H" C0 w& X2 @" m
_autotermed = Updates.UpdateWithReporting(_autotermList, _errors);
4 S% u) [% C# m/ m _issues += _autotermList.Count - _autotermed;' ?* G+ ^0 h; |5 L
}
$ K$ L1 q6 Q, r, K8 z }' ]% j' V" O5 x+ E
transaction.Commit();3 C7 m8 A% k; s6 p; u
}
# y! G1 V6 j8 {. s) @
! u7 @, L( l1 S3 g8 B( g解决方案:. ~: c, e1 r; L: T" x6 n/ |$ R
# l2 h( G' R% b; n
3 e' d- d, S- [9 V- _4 w
1 c- D9 q- Y: B0 k8 ` SQL并未真正提供独占锁定表的方法:它旨在在保持ACID的同时尝试最大程度地提高并发使用率。
( F4 q( w! k% j- t# V4 u/ ?3 S您可以 尝试 在查询中使用这些表提示:
/ P0 T# _/ D/ \6 |$ B4 ]9 ~9 |标签块
1 J/ ^6 r% L* k, n
) T. j! M/ r% B1 H' _) a/ [' X指定将获取的锁应用于表级别。获取的锁的类型取决于正在执行的语句。例如,一个SELECT语句可以获取一个共享锁。通过指定TABLOCK,共享锁将应用于整个表,而不是行或页面级别。如果还指定了HOLDLOCK,则表锁将保持到事务结束。! Q3 g& N. v1 l3 ]% m
& A: N2 S0 Y5 _# O2 W- }TABLOCKX# F2 q( P! ~4 o0 t" O
& H) C. |0 P1 l1 P* }! O! L
指定在表上采用排他锁。* {) \4 @ u* Y6 H* G9 I/ Q+ \
- B4 S/ c9 I9 G8 d
密码锁; u! ]6 o0 J3 Q( c' @
* @5 S4 R" v9 n% }指定将获取并保持更新锁,直到事务完成。UPDLOCK仅在行级别或页面级别为读取操作获取更新锁。如果UPDLOCK与TABLOCK结合使用,或者由于其他原因采用了表级锁定,则将采用排他(X)锁定。
1 e5 z/ {6 d/ v/ D
3 t/ E2 [" d6 d$ y' g2 aXLOCK
$ w& h7 R, C- s" p0 x8 T
3 ~) s$ C8 G- e& Y- V指定在事务完成之前要获取并保持排他锁。如果使用ROWLOCK,PAGLOCK或TABLOCK指定,则排他锁适用于适当的粒度级别。
, D- S: m U+ [2 H9 `$ S; i9 [" b
保持锁定/可串行化
' A" J" f* \9 l5 u" C* t4 p/ X: U8 l8 R: ?) _
通过保持共享锁直到事务完成,使共享锁更具限制性,而不是在不再需要所需的表或数据页时释放共享锁,而不管事务是否已完成。使用与在SERIALIZABLE隔离级别运行的事务相同的语义执行扫描。有关隔离级别的更多信息,请参见SET5 Z3 z. F! L7 j' }
TRANSACTION ISOLATION LEVEL(Transact-SQL)。
. T& \' w3 T$ y; u5 r: \
0 T+ C) Y! k0 f) F或者,您可以尝试设置可事务隔离的SET TRANSACTION ISOLATION LEVEL:
1 M6 l# f9 ^0 _* \: g
7 B: {/ T) { D( L
3 r |1 q9 Z! s5 r' [: F3 L7 o! b语句无法读取已被其他事务修改但尚未提交的数据。/ ?7 R- g G7 O3 D, d
7 C8 e/ V4 j: a$ f
在当前事务完成之前,没有其他事务可以修改当前事务已读取的数据。# B6 ]0 V0 Q3 s0 I: B- @: r
& [* z+ m7 G: T! k
在当前事务完成之前,其他事务不能插入其键值将落入当前事务中任何语句读取的键范围内的新行。
7 ^, _# n7 f5 A# T$ Z
% g! f8 E* B& i# n" ~6 ]' J1 I i8 Z1 A% ?0 p V) H
范围锁位于与事务中执行的每个语句的搜索条件匹配的键值范围内。这将阻止其他事务更新或插入任何符合当前事务执行的任何语句的行。这意味着,如果第二次执行事务中的任何语句,它们将读取相同的行集。范围锁将保持到事务完成为止。这是隔离级别中最严格的限制,因为它锁定整个范围的键并保持锁定,直到事务完成为止。由于并发性较低,因此仅在必要时使用此选项。此选项与在事务中所有SELECT语句中的所有表上设置HOLDLOCK的作用相同。
0 W! l; r" H8 Q: ~% i/ ~/ A' ]+ a7 B( ~8 w! s5 I
但是几乎可以肯定,锁升级将导致阻塞,并且您的用户将被淹死(根据我的经验)。, {5 ^9 u/ M/ d6 K# {) ]4 I
所以…5 \6 N& N! w- n7 ^6 q
请等待,直到有一个计划维护窗口。将数据库设置为单用户模式,进行更改并使其重新联机。 |
|