CockroachDB SQL开发基础 ——事务隔离级别和并发控制介绍 (1)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011782423/article/details/83213137

     CockroachLabs公司的CockroachDB(简称CRDB)是一款适合于海量数据环境下高并发OLTP和OLAP场景的开源分布式NewSQL/HTAP数据库。它采用了新的架构和设计、新的分布式理论和算法,吸收了关系型数据库和NoSQL数据库各自的优点,无需应用级的分库分表,具有SQL模型的简单易用、水平弹性扩展、支持强一致性分布事务、异地多活的高可用、云原生等一系列特点。

     CRDB作为新一代分布式New SQL数据库,它支持ACID事务。ANSI SQL-92 对于事务的隔离级别有明确的定义,这也是大部分传统数据库(MySQL、PostgreSQL、Oracle、DB2、SqlServer等)都遵循的标准。CRDB(PingCap的TiDB也类似)并没有完全支持ANSI标准中的所有隔离级别,它支持如下两种事务隔离级别:

  • 快照隔离级别(Snapshot Isolation,简称SI)
  • 串行化快照隔离级别(Serializable Snapshot Isolation,简称SSI)

     SI隔离级别实现相对简单,性能较好。这种隔离级别类似于 ANSI 标准中的 "可重复读"(简称RR),但是与它又不完全相同:RR会发生“幻像读”,SI不会发生;RR不会发生“写偏斜(write skew)”,而SI会发生。在对性能要求较高,并且“write skew”对于应用不是问题的情况下可使用SI隔离级别。

     相比而言,SSI实现上稍微复杂一些,但仍然能保证较高性能(读写冲突严重的情况下性能会有下降),但是不存在“写偏斜(write skew)”。在CRDB中,SSI是默认的事务隔离级别,用户须根据业务的要求以及实际性能情况,选择合适的隔离级别。

     准确理解CRDB的事务隔离级别和并发控制行为是非常重要的,特别对于应用开发人员来说,如果不了解这些,那么就无法开发出正确的应用。CRDB的官方文档有对其事务的详细介绍,感觉有些复杂,而且有些地方描述含混,甚至是有笔误的地方。下面将结合一些例子对CRDB的两种隔离级别进行说明,力图简洁、清晰地让大家了解其事务隔离级别和并发控制的行为特点,希望对大家能有所帮助。本文介绍SI隔离级别,在下一篇文章中介绍SSI隔离级别。

  1. SI隔离级别

     在CRDB中,运行在SI隔离级别的事务具有如下行为特点:

      (1) 不会发生“脏读”、“不可重复读”和“幻象读”,但是会发生写偏斜。

      (2) CRDB还是会在事务的运行期间,在某些情况下使用"write lock"的,所以叫做Lockless,而不是Lock free。"write lock"的生命周期是事务级的(事务提交或回滚后才释放)。在SI隔离级别下两个运行中的事务,若对相同表的相同记录进行更新,先操作的会获得"write lock",阻塞后操作的事务,直到先操作的事务提交或回滚之后锁才释放,被阻塞的事务才能继续运行;对同一行记录的读和写之间不会发生阻塞。

      下面我们结合示例来进行体会(下面的示例使用的是CockroachDB 2.0.6版本):

      (1) 创建用户表account并插入数据

create table account ( id int, name varchar(8), balance decimal(15,2), primary key (id) );

insert into account values(1,'user1',100) , (2,'user2',100);

      (2) SI隔离级是“可重复读”的

      在两个终端中,分别执行CRDB的命令行工具cockroach sql --insecure,然后在一个终端中交互式运行事务1,在另外一个终端中交互是运行事务2(在后面的示例中都是如此)。运行的时序和事务如操作如下:

 事务1                                                     事务2

 begin;                       

   set transaction isolation

         level snapshot;                           

   select balance                                                            

   from account 

   where id =1;                                        begin;

                                                                  set transaction isolation

                                                                        level snapshot;  

                                                                  update account

                                                                  set balance = balance + 5;

                                                                  where id = 1

      select  balance

      from  account 

      where id = 1;                                     

                                                               commit;

      select balance

      from account

      where id = 1; 

      update account

       set balance = balance - 10;

       where id = 1;

       select balance

       from account

       where id = 1;

       commit;

       说明: 事务1在第一次读取账户id为1的记录的余额值为100,事务2更新相同的记录,把余额加5;事务1再次读取,读到值仍然为100,这说明SI隔离级别不会发生“脏读”。 在事务1第二次读取之后,事务2提交更新,这时id=1的账户的余额为105。事务1进行第三次读取,读取到的余额仍然是100;随后它也更新id=1的账户余额,把余额减少10元,然后第四次读取,得到的值是90。随后,事务1提交,提交时CRDB检测发现id=1的记录在事务1开始后,已经被另一个已提交的事务(事务2)更新过,这就发生了冲突,CRDB会abort事务并回滚它。如下图所示:

        一定要注意:在SI隔离级别下只能读到事务启动时已经提交的其他事务修改的数据,其它事务未提交的数据或在事务启动后其他事务提交的数据是不可见的。此外,SI隔离级别下,不能并发的更新同一行。

      (3) SI隔离级别不会发生"幻像读"

      在两个终端中,分别执行CRDB的命令行工具cockroach sql --insecure,然后在一个终端中交互式运行事务1,在另外一个终端中交互是运行事务2。运行的时序和事务如操作如下:

 事务1                                                     事务2

 begin;                       

   set transaction isolation

         level snapshot;                           

   select count(*)                                                             

   from account 

   where balance =100;                          begin;

                                                                  set transaction isolation

                                                                        level snapshot;  

                                                                  insert into account values(3,'user3',100);

                                                               commit;

     select count(*)

     from account

     where balance = 100;

     commit;

     说明:事务1执行第一次查询之后(查询结果是2,即余额为100元的记录有2条),事务2开始执行insert语句插入一条余额为100元的新记录并提交事务。事务1继续执行第2个查询(在事务2已经提交之后执行),对于CRDB来讲执行结果仍然为2,即看不到事务2已提交的新增满足查询条件的记录,也就是说CRDB的SI隔离级别不会发生"幻像读。

 

     (4) SI隔离级会发生"写偏斜(write skew)"

 事务1                                                  事务2

  begin;                                                 begin;

    set transaction isolation

          level sanpshot;                             set transaction isolation

                                                                     level sanpshot;     

                                                               update account 

                                                               set balance = balance - 200

                                                              where id =2 and (

                                                                   (select balance

                                                                       from account where id =1) +

                                                                   (select balance

                                                                       from account where id = 2)

                                                                   -200

                                                              ) >=0 ;

                                                              commit;

                                        

   update account 

   set balance = balance - 200

   where id =1 and (

        (select balance

             from account

          where id =1)  +

        (select balance

           from account

        where id = 2)

         -200

      )  >=0 ;

commit;

说明:

    A. 上面的事务1和事务2都要保证id为1和id为2的两个账户余额之和始终要大于等于0这一规则,包括在扣减账户余额之后也要遵循这一规则。

    B. 在CRDB中运行这两个事务,在隔离级别SI下是不能保证这一规则的。 事务1和事务2在执行update语句时由于WHERE条件中嵌套执行的SELECT语句得到的两个账户之和都符合条件。事务2先执行update语句,扣减id为2的账户余额200元,事务提交执行成功(这时id=2的账户余额为-100元,满足上面的规则);事务1在执行update语句扣减id 为1的账户余额200元也能执行成功,这是因为SI隔离级别使得事务1看不到事务2对于id=2记录的更新,它看到的仍然是事务1开始时的值(即id=2账户的余额 100 元),所以事务1会更新id=1的记录成功。两个事务执行完后,id为1的账户和id为2的账户余额都是 -100 元,两者之和已经不满足大于等于0这个规则,即发生了 "写偏斜" 。

    C. 对于上面的例子,事务1和事务2也不会发生写写冲突。因为,事务1修改的是id=1的记录,而事务2修改的是id=2的记录,所以能够正常执行成功。

    D. 传统数据库在RR隔离级别下,一般是采用"悲观锁"机制对事务命中的记录行加锁(例如,事务1执行的查询语句会对id=1和id=2两条记录加 "共享行锁",这个锁一直到保持到事务结束才释放),所以能保证不会发生写偏斜。

 

   (5)在SI下的两个事务运行期间,对相同记录进行更新,先操作的会阻塞后操作的事务

  事务1                                             事务2

  begin;                                            begin;

     set transaction isolation

        level sanpshot;                           set transaction isolation

                                                              level sanpshot;     

    update account

    set balance = 120

    where id = 1;

                                                           select *

                                                           from account

                                                           where id = 1;     --不会阻塞

                                                           update account

                                                           set balance = 150

                                                           where id = 1;     --被阻塞,直到事务1结束

    commit;

                                                           --上面阻塞的update执行完成

                                                           select * 

                                                            from account

                                                            where id = 1;

                                                            commit;            -- 读取到余额为150

 

展开阅读全文

PostgreSQL并发控制详解(1)

05-16

http://www.wohedb.com 中文数据库管理系统rnrn第九章 并发控制rn rnrn 本章介绍PostgreSQL的并发控制机制。当两个或多个用户同时访问同一个数据行时,需要使用并发控制机制来维护数据的完整性和一致性。rnrn rnrn9.1 概述rn PostgreSQL使用的是多版本并发控制机制(Multiversion Concurrency Control, MVCC)。多版本并发控制机制的意思是数据库中的每个事务在查询数据时,看到的是数据的快照(一个历史版本),而不是数据的当前状态。在多版本并发控制机制中,读操作不用等待写操作,写操作不用等待读操作,只有在两个事务试图同时更新同一个数据行时,才会有等待出现。多版本并发控制机制可以减少数据库中的锁争用,减少用户的等待时间,提高数据库的性能。rnrn rnrn PostgreSQL同时也提供了接口让应用程序显式对数据进行加锁操作。这些接口支持表级锁和行级锁。此外,还提供了建议锁(advisory lock)这样的锁机制,使得应用程序能够完全自主地控制得到和释放锁的时间。rnrn rnrn9.2事务的隔离级别rn SQL标准定义了四个事务隔离级别,表9-1列出了所有的隔离级别,事务在不同的隔离级别可以中看到的数据是不相同的。rnrn rnrn表9-1. SQL 事务隔离级别rnrn隔离级别rn 脏读(dirty read)rn 不可重复读(nonrepeatable read)rn 影子读(Phantom Read)rn rnRead uncommitted rn 可能 rn 可能rn 可能rn rnRead committed rn 不可能rn 可能rn 可能rn rnRepeatable read rn 不可能rn 不可能rn 可能rn rnSerializable rn 不可能rn 不可能rn 不可能rn rnrn rnrn脏读(dirty read) rnrn一个事务可以读取其它还未提交的事务修改的数据。rnrn rnrn不可重复读(nonrepeatable read) rnrn一个事务重新读取以前读过的数据时,发现该数据被修改过。rnrn rnrn影子读(phantom read) rnrn一个事务两次执行同一个查询,发现第二次查询得到的数据行比第一次查询得到的数据行多。rnrn rnrn PostgreSQL只提供了两种事务隔离级别,分别是Read Committed 和Serializable。使用命令SET TRANSACTION来设置事务的隔离级别。应用程序可以将事务的隔离级别设为Read Uncommitted,但系统会自动将隔离级别设为Read Committed。应用程序也可以将事务的隔离级别设为Repeatable Read,但系统会自动将隔离级别设为Serializable。这样的规则是符合SQL标准的。rnrn rnrn9.2.1 Read Committed隔离级别rn Read Committed是PostgreSQL的默认隔离级别。如果一个事务运行在这个隔离级别,事务发出的SELECT命令只能看见在SELECT命令开始执行以前提交的数据。同一个事务的两个先后执行的 SELECT命令可能会看到不同的数据,因为在其它并发执行的事务可能在第一个SELECT命令执行的过程中被提交。另外,事务发出的SELECT命令可以看到该事务以前发出的更新命令(包括INSERT、DELETE和UPDATE)修改过的数据。rnrn rnrn 命令UPDATE、 DELETE、SELECT FOR UPDATE和SELECT FOR SHARE可以看到的数据和SELECT命令是一样的。如果一个事务执行命令UPDATE(DELETE、SELECT FOR UPDATE或SELECT FOR SHARE),该命令发现一个数据行R1满足自己的搜索条件,同时有另外一个事务T2已经锁住了数据行T1(T2可能正在删除或更新R1),那么T1将进入等待状态,直到T2执行结束(回滚或提交),T1才能继续运行,根据T2的执行结果,T1有两种执行方式:rnrn rnrn(1)如果T2被回滚,T2对R1做的更新将被取消,T1会使用R1原来的值继续运行命令。rnrn(2)如果T2被提交,那要分两种情况:rnrn (a)T2删除了数据行R1,那么T1继续运行时,将忽略R1, R1对T1正在执行的命令是不可见的。rnrn (b)T2修改了数据行R1, 那么T1继续运行时,T1正在执行的命令将看见R1的新的值,同时会重新检查R1的新值是否符合该命令的WHERE子句的搜素条件, 如果符合,将使用R1的新值作为搜索结果,如果不符合,将忽略R1的新值。rnrn rnrn Read Committed隔离级别值保证一个事务的单个命令看到的数据是一致的,不能保证一个事务发出的所有命令看到的数据都是一致的。rnrn rnrn Read Committed隔离级别可以满足大部分应用程序的需要,它使用简单,比serializable隔离级别要快速。但那些使用复杂的查询和更新操作的应用可能需要数据库提供更加严格数据的一致性,这种情况下应该使用Serializable隔离级别。rnrn rnrn9.2.2 Serializable隔离级别rn Serializable是最严格的隔离级别。在这种隔离级别下,所有并发执行的事务的执行结果和单个事务一个一个地执行的结果是一样的。在这种隔离级别下运行的应用程序的逻辑要复杂一些,在事务不符合可串行化要求的而被终止的情况下,应用程序应该能够重新创建被终止的事务并再次请求数据库执行该事务。rnrn rnrn 在Serializable隔离级别下运行的应用程序,一个事务发出的SELECT命令只能看见事务开始运行以前已经提交的数据,看不见在事务开始运行以前没有提交的数据和在事务执行过程中其它并发执行的事务提交的数据。同一个事务的两个先后执行的 SELECT命令看到的数据是一样的。但事务的SELECT命令可以看到该事务以前发出的更新命令(包括INSERT、DELETE和UPDATE)修改过的数据。rnrn rnrn 命令UPDATE、 DELETE、SELECT FOR UPDATE和SELECT FOR SHARE可以看到的数据和SELECT命令是一样的。这些命令只能看到事务开始运行以前已经提交的数据。但是如果一个事务执行命令UPDATE(DELETE、SELECT FOR UPDATE或SELECT FOR SHARE),该命令发现一个数据行R1满足自己的搜索条件,同时有另外一个事务T2已经锁住了数据行T1(T2可能正在删除或更新R1),那么T1将进入等待状态,直到T2执行结束(回滚或提交),T1才能继续运行,根据T2的执行结果,T1有两种执行方式:rnrn rnrn(1)如果T2被回滚,T2对R1做的更新将被取消,T1会使用R1原来的值继续运行命令。rnrn(2)如果T2被提交,而且T2删除或者更新了R1,T1将被回滚,数据库会发出下面的提示信息:rnrn rnrn错误: 当前事务运行在可串行化模式下,与其它并发执行的事务冲突,将被回滚。rnrn rnrn 如果应用程序收到上面的错误信息,应该终止执行当前事务,并重新从头开始执行这个事务。注意,只有含有更新操作的事务可能遇到上面的错误而被终止,只读事务永远都不会被被终止。rnrn rnrn Serializable隔离级别保证每个事务在执行过程中看到完全一致的数据。但应用程序的逻辑会变得很复杂,多个事务若同时更新同一个数据行,其中有一个事务就可能失败,应用程序必须重新执行失败的事务,对数据库的压力就会变大。如果事务的更新逻辑非常复杂,导致在Read Committed隔离级别下可能出现不正确的结果,应该使用Serializable隔离级别。通常如果一个事务的几个连续执行的命令需要看到一样的数据,就应该使用Serializable隔离级别。rnrn rnrn9.2.3 Serializable隔离级别和真正的可串行化rn rnrn Serializable隔离级别并没有保证真正的数学上的可串行化,它只是保证事务在执行时不会出现脏读、不可重复读和Phantom Read。rnrn rnrn 例如,假设有一个叫mytab的表,它的内容如下:rnrn class | value rn-------+-------rn 1 | 10rn 1 | 20rn 2 | 100rn 2 | 200rn rnrn 有一个事务T1:rnrn rnrnBEGINrnrn rnSELECT SUM(value) FROM mytab WHERE class = 1;rn rnINSERT INTO mytab VALUES(2, SUM(value)); rn --注意SUM(value)表示将第一查询的结果插入到表中,这不是正确的INSERT命令语法。rn rnCOMMITrn rnrn rnrn 有另一个事务T2:rnrn rnrnBEGINrnrn rnrnSELECT SUM(value) FROM mytab WHERE class = 2;rnINSERT INTO mytab VALUES(1, SUM(value)); )); --注意SUM(value)表示将第一查询的结果插入到表中,这不是正确的INSERT命令语法rn rnrnCOMMITrn rnrn 假设T1和T2在Serializable隔离级别下被执行。rnrn rnrn 如果T1和T2并发执行,T1得到的结果是30,同时mytab中会多一个数据行(2,30)。T2得到的结果是300,同时mytab中会多一个数据行(1,30)。rnrn rnrn 如果T1先执行,T1结束后,T2再执行,T1得到的结果是30,同时mytab中会多一个数据行(2,30)。T2得到的结果是330,同时mytab中会多一个数据行(1,330)。rnrn rnrn 如果T2先执行,T2结束后,T1再执行,T1得到的结果是330,同时mytab中会多一个数据行(2,330)。T2得到的结果是300,同时mytab中会多一个数据行(1,300)。rnrn rnrn 从上面的例子可以看出在Serializable隔离级别下,并发执行的事务的执行结果与事务串行执行的结果并不相同。rnrn rnrn 如果想保证真正的数学上的可串行化,数据库必须使用谓词锁(predicate lock,SQL Server叫range lock),意思是如果一个事务T1正在执行一个查询,该查询的的WHERE子句存在一个条件表达式E1,那么另外一个事务T2 就不能插入或删除任何满足E1的数据行。例如,一个事务A正在执行一个查询SELECT ... WHERE class = 1,另外一个事务就不能插入、更新或删除任何满足“class=1”的数据行。只有在A提交以后,B才能进行这样的操作。rnrn rnrn 实现谓词锁的代价非常高,而且事务会经常处理等待的状态,增加了查询的响应时间。而且绝大大不部分的应用都不需要数据库保证真正的数学上的可串行化。所以PostgreSQL并没有实现谓词锁,如果应用程序需要数据库保证真正的数学上的可串行化,可以使用PostgreSQL提供的命令对数据显示地进行加锁操作,后面的小节将会讨论如何对数据进行加锁操作。rnrn rnrn9.3 显式加锁rn PostgreSQL提供了多种锁模式来控制对表中的数据的并发访问。如果MVCC 不能满足应用的需求,可以使用这些锁来实现需要的功能。大部分的数据库命令在执行时,数据库会自动锁住相关的资源,不需要用户干预。可以查看视图pg_locks来得到系统中当前的锁信息。rnrn rnrn9.3.1 表级锁rn 下面列出了数据库使用的表级锁的模式和数据库在什么时候会使用这些锁。通常数据库在执行命令时会自动对表加上每个命令需要使用的锁。用户也可以使用命令LOCK对表进行加锁操作。rnrn rnrnACCESS SHARE rnrn只与 ACCESS EXCLUSIVE模式冲突。rnrnSELECT 命令会在引用的表上加上这种模式的锁。 一般来说, 任何只是读取一个表中的数据而不修改表中的数据的查询都会表上加上这种模式的锁。rnrn rnrnROW SHARE rnrn与EXCLUSIVE 和ACCESS EXCLUSIVE 模式冲突。 rnrnSELECT FOR UPDATE 和SELECT FOR SHARE 命令会在引用的表上加上这种模式的锁(同时会对查询引用的其它的表加ACCESS SHARE锁)。 rnrn rnrnROW EXCLUSIVE rnrn与SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE和ACCESS EXCLUSIVE 模式冲突。 rnrnUPDATE, DELETE和INSERT命令会在引用的表上加上这种模式的锁((同时会对查询引用的其它的表加ACCESS SHARE锁)。 一般来说, 任何修改表中的数据的查询都会表上加上这种模式的锁。 rnrn rnrnSHARE UPDATE EXCLUSIVE rnrn与SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE和ACCESS EXCLUSIVE 模式冲突。这个模式保证其它事务不能更新表的定义信息。VACUUM (不带FULL), ANALYZE和CREATE INDEX CONCURRENTLY命令会在引用的表上加上这种模式的锁。rnrn rnrnSHARE rnrn与 ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE和ACCESS EXCLUSIVE 模式冲突。 这个模式保证其它事务不能同时更新表中的数据。 rnrnCREATE INDEX (不带CONCURRENTLY) 命令会在引用的表上加上这种模式的锁。 rnrn rnrnSHARE ROW EXCLUSIVE rnrn与ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE和ACCESS EXCLUSIVE模式冲突。 rnrn任何PostgreSQL命令都不会自动得到这种模式的锁。rnrn rnrnEXCLUSIVE rnrn与 ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE和ACCESS EXCLUSIVE模式冲突 。这种模式只允许其它的事务读表中的数据,不允许其它事务写表中的数据。PostgreSQL命令不会自动在任何用户创建的表上加这种模式的锁。只有在访问系统表时才会用到这种模式的锁。rnrn rnrnACCESS EXCLUSIVE rnrn与所有的锁模式都冲突 (ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, and ACCESS EXCLUSIVE)。这个锁模式保证只有拥有该锁的事务才能访问表,其它事务不能读也不能写这个表。ALTER TABLE, DROP TABLE, TRUNCATE, REINDEX, CLUSTER和VACUUM FULL 命令会得到这种模式的锁。LOCK TABLE命令如果没有指定锁的模式,默认使用这种模式来锁住表。 rnrn 提示: 只有ACCESS EXCLUSIVE 模式的锁才能阻止SELECT (不带FOR UPDATE/SHARE)命令被执行。rnrn rnrn 事务一旦得到某个模式的锁,该锁只有在事务结束以后才会被释放。但事务在创建了一个savepoint以后得到的锁会在事务被回滚该savepoint以后立即被释放。rnrn rnrn 如果两个锁模式互相冲突,那么两个不同的事务不能在同一个表上同时拥有这两种模式的锁。如果两个锁模式不是互相冲突的,那么两个不同的事务可以在同一个表上同时拥有这两种模式的锁。但同一个事务可以同时在同一个表上拥有两个互相冲突的锁模式。例如,ACCESS EXCLUSIVE与ACCESS SHARE是互相冲突的锁模式,但一个事务可以先在一个表上得到ACCESS EXCLUSIVE模式的锁,然后又可以在该表上得到ACCESS SHARE模式的锁。表9-2列出了锁冲突矩阵,其中的X表示冲突,空白表示不冲突。rnrn rnrn 注意,有些锁模式与自身是冲突的,例如,两个事务不能在同一表上同时拥有ACCESS EXCLUSIVE模式的锁。注意,有些锁模式与自身是不冲突的,例如,两个事务可以在同一表上同时拥有ACCESS SHARE模式的锁。rnrn rnrn表9-2. 锁冲突矩阵 rnrn请求的锁模式rn 当前锁模式rn rnACCESS SHARErn ROW SHARErn ROW EXCLUSIVErn SHARE UPDATE EXCLUSIVErn SHARErn SHARE ROW EXCLUSIVErn EXCLUSIVErn ACCESS EXCLUSIVErn rnACCESS SHARErn rn rn rn rn rn rn rn Xrn rnROW SHARErn rn rn rn rn rn rn Xrn Xrn rnROW EXCLUSIVErn rn rn rn rn Xrn Xrn Xrn Xrn rnSHARE UPDATE EXCLUSIVErn rn rn rn Xrn Xrn Xrn Xrn Xrn rnSHARErn rn rn Xrn Xrn rn Xrn Xrn Xrn rnSHARE ROW EXCLUSIVErn rn rn Xrn Xrn Xrn Xrn Xrn Xrn rnEXCLUSIVErn rn Xrn Xrn Xrn Xrn Xrn Xrn Xrn rnACCESS EXCLUSIVErn Xrn Xrn Xrn Xrn Xrn Xrn Xrn Xrn rn rn 论坛

【请教】Linq to sql 并发控制

09-14

项目要用linq to sql 对数据进行更新,看了网上的文章说可以通过timestamp类型的字段做版本控制。rnrn我在表里添加了timestamp字段,并定义了一个强类型的实体类。代码如下:rnrn [global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.TRS_Branch")]rn public partial class TRS_Branch : INotifyPropertyChanging, INotifyPropertyChangedrn rn rn private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);rn rn private int _UID;rn rn private string _BranchCode;rn rn private string _BranchName;rn rn private string _Descr;rn rn private string _Location;rn rn private string _Updated_Date;rn rn private string _Updated_time;rn rn private System.Data.Linq.Binary _Time_Stamp;rn rn #region Extensibility Method Definitionsrn partial void OnLoaded();rn partial void OnValidate(System.Data.Linq.ChangeAction action);rn partial void OnCreated();rn partial void OnUIDChanging(int value);rn partial void OnUIDChanged();rn partial void OnBranchCodeChanging(string value);rn partial void OnBranchCodeChanged();rn partial void OnBranchNameChanging(string value);rn partial void OnBranchNameChanged();rn partial void OnDescrChanging(string value);rn partial void OnDescrChanged();rn partial void OnLocationChanging(string value);rn partial void OnLocationChanged();rn partial void OnUpdated_DateChanging(string value);rn partial void OnUpdated_DateChanged();rn partial void OnUpdated_timeChanging(string value);rn partial void OnUpdated_timeChanged();rn partial void OnTime_StampChanging(System.Data.Linq.Binary value);rn partial void OnTime_StampChanged();rn #endregionrn rn public TRS_Branch()rn rn OnCreated();rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]rn public int UIDrn rn getrn rn return this._UID;rn rn setrn rn if ((this._UID != value))rn rn this.OnUIDChanging(value);rn this.SendPropertyChanging();rn this._UID = value;rn this.SendPropertyChanged("UID");rn this.OnUIDChanged();rn rn rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_BranchCode", DbType="VarChar(20) NOT NULL", CanBeNull=false)]rn public string BranchCodern rn getrn rn return this._BranchCode;rn rn setrn rn if ((this._BranchCode != value))rn rn this.OnBranchCodeChanging(value);rn this.SendPropertyChanging();rn this._BranchCode = value;rn this.SendPropertyChanged("BranchCode");rn this.OnBranchCodeChanged();rn rn rn rnrn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_BranchName", DbType = "VarChar(20) NOT NULL", CanBeNull = false)]rn public string BranchNamern rn getrn rn return this._BranchName;rn rn setrn rn if ((this._BranchName != value))rn rn this.OnBranchNameChanging(value);rn this.SendPropertyChanging();rn this._BranchName = value;rn this.SendPropertyChanged("BranchName");rn this.OnBranchNameChanged();rn rn rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Descr", DbType="VarChar(50) NOT NULL", CanBeNull=false)]rn public string Descrrn rn getrn rn return this._Descr;rn rn setrn rn if ((this._Descr != value))rn rn this.OnDescrChanging(value);rn this.SendPropertyChanging();rn this._Descr = value;rn this.SendPropertyChanged("Descr");rn this.OnDescrChanged();rn rn rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Location", DbType="VarChar(10) NOT NULL", CanBeNull=false)]rn public string Locationrn rn getrn rn return this._Location;rn rn setrn rn if ((this._Location != value))rn rn this.OnLocationChanging(value);rn this.SendPropertyChanging();rn this._Location = value;rn this.SendPropertyChanged("Location");rn this.OnLocationChanged();rn rn rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Updated_Date", DbType="VarChar(10)")]rn public string Updated_Datern rn getrn rn return this._Updated_Date;rn rn setrn rn if ((this._Updated_Date != value))rn rn this.OnUpdated_DateChanging(value);rn this.SendPropertyChanging();rn this._Updated_Date = value;rn this.SendPropertyChanged("Updated_Date");rn this.OnUpdated_DateChanged();rn rn rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Updated_time", DbType="VarChar(8)")]rn public string Updated_timern rn getrn rn return this._Updated_time;rn rn setrn rn if ((this._Updated_time != value))rn rn this.OnUpdated_timeChanging(value);rn this.SendPropertyChanging();rn this._Updated_time = value;rn this.SendPropertyChanged("Updated_time");rn this.OnUpdated_timeChanged();rn rn rn rn rn [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Time_Stamp", AutoSync=AutoSync.Always, DbType="rowversion", IsDbGenerated=true, IsVersion=true)]rn public System.Data.Linq.Binary Time_Stamprn rn getrn rn return this._Time_Stamp;rn rn setrn rn if ((this._Time_Stamp != value))rn rn this.OnTime_StampChanging(value);rn this.SendPropertyChanging();rn this._Time_Stamp = value;rn this.SendPropertyChanged("Time_Stamp");rn this.OnTime_StampChanged();rn rn rn rn rn public event PropertyChangingEventHandler PropertyChanging;rn rn public event PropertyChangedEventHandler PropertyChanged;rn rn protected virtual void SendPropertyChanging()rn rn if ((this.PropertyChanging != null))rn rn this.PropertyChanging(this, emptyChangingEventArgs);rn rn rn rn protected virtual void SendPropertyChanged(String propertyName)rn rn if ((this.PropertyChanged != null))rn rn this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));rn rn rn rnrn然后在页面上用linq to sql 取到数据,然后更新数据。rnrnusing (BranchTableDataContext context2 = new BranchTableDataContext(conn))rn rn rn TRS_Branch gb = context2.TRS_Branch.First();rnrn gb.Descr = "Updated";rnrn context2.SubmitChanges();rnrn rnrnrnrn最后同时运行两个页面,没有抛ChangeConflictException的异常都更新成功。rnrn这是为什么呢?有没有linq to sql 控制并发的例子呢。rnrn请各位大大帮忙解答 论坛

没有更多推荐了,返回首页