攻城狮小明
IP:
0关注数
0粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·21

全部
问答
动态
项目
学习
专栏
攻城狮小明

复制延迟案例(4)-一致前缀读

该案例违反因果律。 想象先生和夫人之间的对话:这两句之间有因果关系:夫人听到先生的问题并回答该问题。想象第三者老王在通过从节点听对话。 夫人说的内容是从一个延迟很低的从节点读取,但先生所说的内容,从节点的延迟要大的多,如图-5,于是该观察者会听到:对观察者来说,看起来好像夫人在先生发问前就回答了问题。分片来防止这种异常,需要新类型的保证:一致前缀读(consistent prefix reads),若一系列写入按某个顺序发生,那么任何人读取这些写入时,也会看见它们以同样的顺序出现。这是分片数据库中的特殊问题。若数据库总以相同顺序写入,则读总会看到一致的序列,不会发生这种异常。许多分布式数据库中,不同分片独立运行,因此不存在全局写入顺序。这就导致,当用户从DB读数据时,可能会看到DB某些部分处于较旧状态,某些处于较新状态。解决方案确保任何具有因果顺序关系的写人都交给一个完成,但该方案实际的实现效率大打折扣 。
0
0
0
浏览量2022
攻城狮小明

复制延迟案例(2)-读己之写

许多应用让用户提交一些数据,然后查看提交的内容。如客户DB中的记录或某主题的评论。提交新数据必须发送到主节点,但当用户读数据时,可能从【从节点】读取。这对读密集和偶尔写入的负载很合适。但异步复制则有问题,如图-3:若用户在写后马上查看数据,则新数据可能尚未到达副本。对用户而言,看起来好像是刚提交的数据丢了,用户会不高兴。此时,需“写后读一致性(read-after-write consistency)”,也称读写一致性(read-your-writes consistency)。该机制保证:若用户重新加载页面,他们总能看到自己最近提交的更新。但对其他用户没有任何保证,这些用户的更新可能会在稍后才能刷新看到。主从复制实现 写后读一致性若用户访问:可能会被修改的内容,读主否则,读从这要求实际查询前,就得考虑内容是否可能会被修改。如社交网络上的用户个人资料信息通常只能由用户本人编辑,而其他人无法编辑。简单规则:从主节点读取用户自己的档案,在从节点读取其他用户的档案。若应用大部分内容都可能被用户编辑,则上面方案就没啥用,因为大部分内容都读主节点,导致丧失读操作的扩展性。就得考虑其他标准来决定是否读主。如跟踪最近更新时间,若更新后1min 内,则总是读主节点。并监控从节点的复制延迟程度,避免对任意比主节点滞后超过一分钟的从节点发出查询。客户端还可记住最近更新时的时间戳,并附带在读请求中,据此,系统可确保对该用户提供读服务时,都应该至少包含了该时间戳的更新。若当前从节点不够新,可读另一个从节点或一直等待从节点直到收到最近的更新。时间戳可以是逻辑时间戳(指示写入顺序的日志序列号)或实际系统时钟。若副本分布在多IDC(如考虑与用户的地理接近及高可用性),会更复杂。必须先把请求路由到主节点所在IDC(该IDC可能离用户很远)。若同一用户从多个设备请求服务,如桌面浏览器和移动APP,就更复杂了。这时,可能就需提供跨设备的写后读一致性,即若用户在某设备输入一些信息,然后在另一个设备查看,则应该看到刚输入的信息。此时,还需考虑:记住用户上次更新时间戳的方法实现更困难,因为一台设备上运行的程序不知道另一台设备上发生啥。元数据需要一个中心存储,做到全局共享若副本分布在多IDC,无法保证来自不同设备的连接会路由到同一IDC。如用户台式计算机使用家庭宽带连接,而移动设备使用蜂窝数据网络,则设备的网络路线可能完全不同。若方案要求必须读主,则首先要确保来自不同设备的请求路由到同一IDC。
0
0
0
浏览量2051
攻城狮小明

复制延迟案例(3)-单调读

前面异步复制读异常的第二个案例,出现用户数据向后回滚的怪状。若用户从不同【从节点】多次读取,就可能这样。如图-4显示用户2345两次进行相同查询:首先查询延迟很小的从节点然后是延迟较大的从节点若用户刷新网页,而每个请求被路由到一个随机的服务器,这种情况是很有可能的。第一个查询返回最近由用户1234添加的评论,但第二个查询不返回任何东西,因为滞后的从节点还没有拉取写入的内容。效果上相比第一个查询,第二个查询是在更早的时间点来观察系统。若第一个查询未返回任何内容,则问题不大,因为用户2345可能不知道用户1234最近添加了评论但若用户2345先看见用户1234的评论,然后又看到它消失,则对用户2345,就会感觉头大单调读保证这种异常不会发生。这是比强一致性(strong consistency)弱,但比最终一致性强的保证。当读取数据时,可能会看到一个旧值;单调读取仅意味着若一个用户顺序多次读取,则不会看到时间后退,即若先前读取到较新的数据,后续读取不会得到更旧数据。实现单调读取的一种方案:确保每个用户总从同一副本读取(不同用户可读不同副本)。如基于用户ID散列选择副本,而非随机选择副本。但若该副本失败,用户的查询将需重新路由到另一个副本。
0
0
0
浏览量2029
攻城狮小明

复制延迟案例(1)-最终一致性

复制延迟的案例容忍节点故障只是使用复制的一个原因。其它原因包括:可扩展性,采用多节点处理更多请求低延迟,让副本在地理位置上更接近用户主从复制要求所有写请求都主节点处理,从节点只能处理。读多写少场景,这是不错的选择:创建多个从节点,将读请求分散到所有的从节点,从而减轻主节点的负载,并允许向最近的副本发送读请求。这种可伸缩结构下,只需添加更多从节点,就能提高读请求的服务吞吐量。但这只适于异步复制,若试图同步复制到所有从节点,则单节点故障或网络中断将使整个系统无法写入。且节点越多,故障概率越高,所以完全同步的配置很不可靠。最终一致性若应用正好从一个异步的从节点读取时,而该从节点落后于主节点,它可能会看到过期数据,导致数据库中不一致:由于并非所有写入都反映在从节点,若同时对主、从节点发起相同查询,可能得到不同结果。这种不一致只是暂时的状态,若停止写DB,并等待一段时间,从节点最终会赶上并与主节点保持一致。不只有NoSQL数据库是最终一致的:关系型数据库中的异步复制追随者也有相同的特性。“最终”一词故意含糊不清,理论上,副本落后的程度无限制。正常操作中,主节点和从节点上完成写操作之间的时间延迟(复制滞后)可能不足1s,这样的滞后,在实践中通常不会导致太大影响。但若系统在接近极限情况下运行或网络存在问题,延迟可轻松超过几秒甚至几分钟。
0
0
0
浏览量2038
攻城狮小明

复制延迟案例

复制延迟实际案例,助理更好的理解
0
0
0
浏览量2186
攻城狮小明

精通Java事务编程(6)-可串行化隔离级别之真串行

RC 和 快照隔离 级别可防止某些竞争条件,但并非全部。一些棘手案例,如写偏斜 和 幻读,会发现可悲情况:隔离级别难理解,且不同DB实现不一(如RR含义天差地别)若检查应用层代码很难判断特定隔离级别下是否安全,尤其是大型系统,无法预测各种并发无检测竞争条件的好工具。理论上,静态分析可能有所帮助,但更多技术还没法实际应用。并发问题测试也很难,一切取决于时机而这些还不是新问题,1970s引入了较弱隔离级别以来一直这样。研究人员的答案都很简单:使用可串行化隔离级别!可串行化隔离是最强隔离级别。保证即使事务可以并发执行,但最终结果和串行执行一样。因此数据库保证,若事务在单独运行时正常运行,则它们在并发运行时仍正确,即DB能防止所有可能的竞争条件。若可串行化比弱隔离级别好得多,那为何没啥人用?支持可串行化DB都使用如下三种技术之一:严格串行顺序执行事务两阶段锁定(2PL, two-phase locking),几十年来几乎唯一可行选择乐观并发控制技术,如可串行化快照隔离本文主要在单节点DB下讨论这些技术。3.1 真的串行执行避免并发最简单方法就是完全不并发:即在一个线程上按序执行事务。这完全回避了检测、防止事务冲突。看着很直接的想法,但DB设计人员在 2007 年才确信,单线程循环执行事务可行。若多线程并发在过去的30年中被认为是获得良好性能的关键所在,那么究竟是什么改变致使单线程执行?如下两个进展促使我们重新审视:RAM越来越便宜,许多场景现在都能将完整活动数据集加载到内存。当事务所需数据都在内存,事务处理的执行速度要比等待数据从磁盘加载时快得多。数据库设计人员意识到 OLTP 事务通常执行很快,而且只产生少量读写操作。相比之下,长时间运行的分析查询通常只读,可在一致性快照(使用快照隔离)上运行,而不需要运行在串行主循环里串行执行事务的方法在 VoltDB/H-Store,Redis 和 Datomic 中实现。单线程执行的系统有时可以比支持并发的系统效率更好,尤其是可以避免锁开销。但吞吐量上限为单 CPU 核吞吐量。为充分利用单线程,需要与传统形式的事务做出不同调整。3.1.1 存储过程中封装事务DB早期阶段,采用事务包含整个用户操作流程。如预订机票涉及多阶段(搜索路线,票价和可用座位,决定行程,在行程的某航班订座,输入乘客信息,最后付款)。DB设计者认为,若整个过程是个事务,则它就可被原子化执行。但人类做出决定并回应的速度缓慢。若DB事务需等待用户输入,则DB需支持潜在的大量并发事务,其中大部分是空闲的。大多DB不能高效完成这项工作,因此几乎所有的 OLTP 应用程序都避免在事务中等待交互式的用户输入,以此来保持事务简短。在 Web 上,这意味着事务在同一个 HTTP 请求中被提交,而不会跨越多个请求。一个新的 HTTP 请求就开始一个新事务。即使已经将人为交互从关键路径中排除,事务仍以交互式客户端 / 服务器风格执行,一次一个请求语句。应用程序提交查询,读取结果,可能根据第一个查询的结果进行另一个查询,依此类推。查询和结果在应用程序代码(在一台机器上运行)和数据库服务器(在另一台机器上)之间来回发送。在这种交互式的事务方式中,应用程序和数据库之间的网络通信耗费了大量的时间。如果不允许在数据库中进行并发处理,且一次只处理一个事务,则吞吐量将会非常糟糕,因为数据库大部分的时间都花费在等待应用程序发出当前事务的下一个查询。在这种数据库中,为了获得合理的性能,需同时处理多个事务。因此,采用单线程串行执行的系统不支持交互式的多语句事务。应用程序必须提前将整个事务代码作为存储过程提交给DB。这些方法差异如图-9。将事务所需数据都加载到内存,则存储过程可更快执行,而无需等待网络或磁盘I/O。3.1.2 存储过程的优缺点存储过程在关系型DB已存在一段时间,自 1999 年以来一直是 SQL 标准(SQL/PSM)一部分,但名声有点不好:每个DB厂商都有自己的存储过程语言(Oracle的PL/SQL,SQL Server的T-SQL,PostgreSQL的PL/pgSQL 等)。这些语言并未跟上通用编程语言发展,所以看起来丑陋过时,而且缺乏大多数编程语言中能找到的库的生态在DB中运行代码难以管理:与应用服务器相比,更难调试,更难保持版本控制和部署,更难测试,难集成到指标收集系统来进行监控DB通常比应用服务器对性能敏感,因为单个数据库实例通常由许多应用服务器共享。DB中一个写得不好的存储过程(如占用大量内存或 CPU 时间)会比在应用服务器中相同的代码造成更多的麻烦但这些问题都能克服。现代的存储过程实现放弃了 PL/SQL,而是使用现有的通用编程语言:VoltDB 使用 Java 或 Groovy,Datomic 使用 Java 或 Clojure,而 Redis 使用 Lua。存储过程与内存存储使得在单个线程上执行所有事务变得可行。由于不需要等待 I/O,且避免了并发控制机制的开销,它们可以在单个线程上实现相当好的吞吐量。VoltDB 还使用存储过程进行复制:但不是将事务的写入结果从一个节点复制到另一个节点,而是在每个节点上执行相同的存储过程。因此 VoltDB 要求存储过程是 确定性的(在不同的节点上运行时,它们必须产生相同的结果)。举个例子,如果事务需要使用当前的日期和时间,则必须通过特殊的确定性 API 来实现。3.1.3 分区串行执行所有事务使并发控制更简单,但DB事务吞吐量被限制为单机单核速度。虽然只读事务能使用快照隔离在其它地方执行,但对写入吞吐量较高应用,单线程事务处理器可能成为一个严重瓶颈。为伸缩至多个CPU核和多个节点,可对数据分区,VoltDB 支持这样做。若找到一种对数据集分区方法,以便每个事务只需在单分区中读写数据,则每个分区就能拥有自己独立运行的事务处理线程。此时,可为每个分区指派一个独立CPU核,则 DB 事务吞吐量就能与 CPU 核数保持线性伸缩。但对跨分区的任何事务,DB必须在涉及的所有分区之间协调事务。存储过程需跨越所有分区加锁执行,以确保整个系统可串行化。由于跨分区事务具有额外协调开销,所以它们比单分区事务慢得多。 VoltDB 报告的吞吐量大约是每秒 1000 个跨分区写入,比单分区吞吐量低几个数量级,并且不能通过增加更多的机器来扩展性能。事务是否可以是划分至单个分区很大程度上取决于应用数据的结构。简单KV数据通常可以非常容易地进行分区,但是具有多个次级索引的数据可能需要大量跨分区协调。3.1.4 小结满足如下特定约束条件,串行执行事务可实现串行化隔离:事务简短高效,只要有一个缓慢事务,就会拖慢影响所有其它事务性能仅限于活跃数据集完全能放入内存的case。很少访问的数据可能会被移动到磁盘,但万一需要在单线程事务中访问,就会拖累系统 1。写吞吐量必须低到能在单 CPU 核处理,否则需要分区,事务划分至单个分区,最好无需跨分区协调事务跨分区事务虽然也能支持,但比例必须很小若事务需访问不在内存中的数据,最佳实践可能是中止事务,异步将数据提取到内存,同时继续处理其他事务,然后在数据加载完毕时重启事务,这被称为 反缓存(anti-caching)。 ↩︎
0
0
0
浏览量2013
攻城狮小明

精通Java事务编程(8)-可串行化隔离级别之可串行化的快照隔离

本系列文章描述了DB并发控制的黯淡:2PL虽保证了串行化,但性能和扩展不好性能良好的弱隔离级别,但易出现各种竞争条件(丢失更新,写倾斜,幻读串行化的隔离级别和高性能就是相互矛盾的吗?也许不是,一个称为可串行化快照隔离(SSI, serializable snapshot isolation)算法很有前途。提供完整的可串行化保证,而性能与快照隔离相比只有很小性能损失。 SSI在 2008 年首次被提出,如今既用于单节点DB(PostgreSQL9.1后的可串行化)和分布式DB(FoundationDB)。由于 SSI 与其他并发控制机制相比还很年轻,还在实践中证明自己。3.3.1 悲观锁、乐观锁两阶段锁是一种 悲观锁机制(pessimistic) ,其设计原则:若有操作可能出错(如与其他事务发生锁冲突),则直接放弃,等待直到绝对安全。和多线程编程中的互斥锁一致。某种意义上,串行执行是很悲观的:事务期间,每个事务对整个DB(或DB的一个分区)持有互斥锁,我们只能假定每笔事务执行够快、短时持锁,来稍微弥补悲观色彩相比之下,串行化快照隔离 是一种 乐观锁。如若存在潜在冲突,也不阻止事务,而是继续执行事务,寄希望于一切平安。而当事务想提交时(只有可串行化的事务才被允许提交。),DB会检查是否冲突(即违反隔离性原则):若是,则中止事务并重试。乐观锁是古老的想法,其优缺点争论已久。若存在很多冲突,则性能不佳,大量事务需中止。若系统已接近最大吞吐量,重试的额外负载会使系统性能更差。但若系统有足够性能提升空间,且事务之间争用不大,乐观锁比悲观锁更高效。可交换的原子操作能减少争用:如若多个事务同时增加某计数器,则应用增量的顺序(只要计数器不在同一个事务中读取)就无关紧要,所以并发增量可全部应用且无需冲突。SSI基于快照隔离,即事务中的所有读取都基于DB的一致性快照(参阅本文的快照隔离、可重复读),这和早期乐观锁的主要区别。在快照隔离基础上,SSI新增一种算法检测写入之间的串行化冲突,并确定要中止哪些事务。3.3.2 基于过期条件来决策讨论写倾斜时,有一种场景:事务先从DB读一些数据,根据查询结果决定采取后续操作,如修改数据。但快照隔离下,数据可能在查询期间就已被其他事务修改,导致原事务在提交时决策的依据信息已变。即事务基于某些前提而行动,事务开始时条件成立,如目前有两名医生正在值班,当事务提交时,数据可能已改变,前提已不再成立。当应用执行查询时(如当前有多少医生在值班),DB本身不知道应用会如何使用该查询结果。为了安全,DB假定对该结果集的变更都可能会使该事务中的写无效。 即事务中的查询与写可能存在因果依赖关系。为提供可串行化隔离,DB必须检测事务是否会修改其它事务的查询结果,并在此情况下中止写事务。DB如何知道查询结果是否已变?可分为如下case:读取是否作用于一个(即将)过期的MVCC对象(读取之前已经有未提交的写入)检查写是否影响即将完成的读取(读取后,又有新写入)3.3.3 检测旧MVCC读取为防止这种异常,DB需跟踪一个事务由于MVCC可见性规则而被忽略的其它事务写。当事务提交时,DB会检查是否存在被忽略的写现在已被提交的,若是,则当前事务必须中止。为何要等到提交?当检测到读旧值,为何不立即中止事务43,考虑如下场景:若事务43是只读事务,则无需中止,因为无写倾斜风险当事务43读DB 时,DB还不知道事务是否要稍后执行写操作此外,事务42可能在事务43提交时,被中止或仍处于未被提交,因此读取的并非旧值通过避免不必要的中止,SSI可高效支持那些需在一致性快照中运行很长时间的读事务。3.3.4 检测写是否影响之前的读读取数据后,另一个事务修改了数据:3.3.5 性能许多工程细节会影响算法实际效果。如一个需权衡考虑的是跟踪事务的读、写的粒度:若DB详细跟踪每个事务的操作(细粒度),确实能准确确定哪些事务需中止,但记录元数据的开销可能也很大而跟踪速度更快时(粗粒度),可能导致更多不必要的事务中止有的case读过期数据不会造成太大影响:这还是完全取决于具体场景,有时可确信执行结果都是可串行化的,PostgreSQL 使用该理论减少不必要的中止。相比于2PL,可串行化快照隔离最大优点:事务无需阻塞等待其它事务所持有的锁。这和快照隔离一样,读写不互相阻塞。这使查询延迟更稳定、可预测。尤其是只读查询可运行在一致快照,无需任何锁,对读密集系统友好。相比于串行执行,可串行化快照隔可突破单CPU核吞吐量限制:FoundationDB将检测到的串行化冲突分布在多台机器,从而提高吞吐量。即使数据可能跨多台机器分区,事务也能在保证可串行化隔离等级同时,读写多个分区中的数据。事务中止率会显著影响SSI性能。如长时间读、写数据的事务很可能会发生冲突并中止,因此SSI要求读写型事务尽量短(但只读的长事务则没问题)。总体上,对慢事务,SSI比2PL或串行执行更能容忍。
0
0
0
浏览量2011
攻城狮小明

精通Java事务编程(9)-总结

事务作为抽象层,允许应用忽略DB 内部一些复杂并发问题和某些硬件、软件故障,简化应用层的处理逻辑:事务中止(transaction abort),而应用仅需重试。对复杂访问模式,事务可大大减少需要考虑的潜在错误情景数量。如没有事务,各种错误情况(进程崩溃,网络中断,停电,磁盘已满,意外并发)意味着数据可能各种不一致。如非规范化的数据可能很容易与源数据不同步。没有事务处理,就很难推断复杂的交互访问可能对数据库造成影响。隔离等级要点:脏读客户端读到另一客户端尚未提交的写。读已提交 或更强的隔离级别可防止脏写一个客户端覆写了另一客户端尚未提交的写。几乎所有事务实现都可防止读倾斜(不可重复读)同一个事务中,客户端在不同时间点看见数据库不同值。快照隔离 用于解决这问题,允许事务从某特定时间点的一致性快照中读数据,MVCC实现更新丢失两个客户端同时执行 读取 - 修改 - 写入。其中一个写操作,在没有合并另一个写入变更情况下,直接覆盖了另一个写结果。所以导致数据丢失。快照隔离的一些实现可自动防止这种异常,而另一些实现则需手动锁定(SELECT FOR UPDATE)写倾斜一个事务读取一些东西,根据它所看值决定,并将该决定写数据库。但写时,该决定的前提不再true。只有可串行化隔离才能防止幻读事务读取某些符合查询条件的对象,同时另一客户端写,改变了先前查询结果。快照隔离可防简单的幻读,但写倾斜的幻读需特殊处理,如采用索引范围锁定。弱隔离级别可防止上述的一些异常,但还得应用程序开发人员手动处理其它复杂 case,如显式加锁。只有可串行化隔离级别能防所有这些问题,有三种不同实现方案:严格串行执行事务若每个事务的执行很快,且单CPU核即可满足事务吞吐要求,这是简单有效的选择2PL数十年来,一直是可串行化的标准实现,但许多应用考虑性能而放弃使用之可串行化快照隔离(SSI)最新算法,避免先前方案的大部分缺点。使用乐观锁机制,允许事务并发执行而不互相阻塞。仅当事务提交时,才检查可能的冲突,若发现违背串行化,则中止事务
0
0
0
浏览量2009
攻城狮小明

精通Java事务编程(5)-写倾斜与幻读

多个事务并发写相同对象时,会出现脏写、更新丢失两种竞争条件。为避免数据不一致,可:借助DB内置机制或通过显式加锁以执行原子写操作。但这还不是并发写可能导致的全部问题。2.4.1 值班程序医院通常会同时要求几个医生待命,前提是至少有一位医生在待命。医生可放弃他们的班次(如若自己生病了),只要至少有一个同事在这天的班中继续工作。Alice、Bob两位值班医生都病了,所以他们都决定请假。但他们恰在同一时刻点击调班按钮每笔事务总先检查是否至少有两名医生目前在值班。若是,则有一名医生可安全离开去休班。由于DB使用快照隔离,两次检查都返回2,所以两事务都进入下一阶段:Alice更新自己的记录为休班Bob也更新自己的记录两个事务都成功提交,最后结果是无医生值班,显然违反了至少有一名医生得值班的业务需求。2.4.2 写倾斜这种异常即写倾斜,不是脏写、丢失更新。这俩事务更新的是两个不同对象(Alice 和 Bob 各自值班记录)。这里发生的冲突不是那么明显,但显然也是竞态:若两个事务串行,则第二个医生就不能歇班。异常行为只有在事务并发时才可能。可将写倾斜视为【广义的丢失更新】。即若两事务读取相同一组对象,然后更新其中一部分:不同事务,更新不同对象,则可能发生写倾斜不同事务,更新同一对象,则可能脏写或丢失更新很多方法可防止丢失更新。但对写倾斜,方案更受限:由于涉及多对象,单对象的原子操作无效基于快照隔离来实现自动检测丢失更新也有问题:PostgreSQL可重复读,MySQL/InnoDB 可重复读,Oracle可串行化或SQL Server快照隔离级别中,都不支持自动检测写倾斜。自动防止写倾斜要求真正的可串行化隔离某些DB支持自定义约束,然后由DB强制执行(如唯一性,外键约束或特定值限制)。但为指定至少有一名医生必须在线,涉及多个对象的约束,大多DB都未内置这种约束,但你可使用触发器或物化视图来实现类似约束若无法使用可串行化,则次优方案可能是显式锁定事务依赖的行:BEGIN TRANSACTION; SELECT * FROM doctors WHERE on_call = TRUE # 告诉DB锁定返回的所有结果行,以用于更新 AND shift_id = 1234 FOR UPDATE; UPDATE doctors SET on_call = FALSE WHERE name = 'Alice' AND shift_id = 1234; COMMIT;2.4.3 写倾斜case理解到写倾斜的本质后,容易注意到更多case:会议室预订系统不能在同一时间对同一会议室进行多次预订。当有人想要预订时,首先检查是否存在相互冲突的预订(即预订时间范围重叠的同一房间),若无,则创建会议(参阅示例-2)1例-2 会议室预订系统,避免重复预订(在快照级别隔离下不安全)BEGIN TRANSACTION; -- 检查所有现存的与 12:00~13:00 重叠的预定 SELECT COUNT(*) FROM bookings WHERE room_id = 123 AND end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00'; -- 若之前的查询返回 0 INSERT INTO bookings(room_id, start_time, end_time, user_id) VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666); COMMIT;快照级别隔离无法防止并发用户预订同一会议室。为避免预订冲突,需可串行化隔离级别。多人游戏例-1中,使用一个锁来防止丢失更新(即两个玩家不能同时移动同一数字)。但锁不妨碍玩家将两个不同数字移动到棋盘的相同位置或其他违反游戏规则的行为。可能需更多约束,否则很容易发生写倾斜。抢注用户名在每个用户拥有唯一用户名的网站上,两个用户可能会尝试同时创建具有相同用户名的帐户。可采用事务检查名称是否被抢占,若无,则使用该名称创建账户。但和之前案例类似,快照隔离下不安全。但唯一约束是简单方案(第二个事务在提交时会因为违反用户名唯一约束而被中止)防止双重开支支付或积分服务一般需检查用户的支付数额不超过余额。可通过在用户帐户中插入一个临时支出项目,列出帐户中的所有项目,并检查总和是否为正值。由于写倾斜,可能发生两个支出项目同时插入,两个交易都不超额,但一起会导致余额变为负值。2.4.4 导致写倾斜的幻读所有这些案例都遵循类似模式:
0
0
0
浏览量2016
攻城狮小明

精通Java事务编程(1)-深入理解事务

苛刻的数据存储系统中,很多可能出错的case:数据库软件、硬件可能随时失效(包括正在执行写操作的过程中)应用程序可能随时崩溃(包括一系列操作的中间某步)网络中断可能会意外切断数据库与应用的连接,或数据库之间的连接。多个客户端可能同时写入DB,导致数据覆盖客户端可能读到无意义的、部分更新的数据客户端之间由于边界条件竞争所引入的各种奇怪问题为实现高可靠,系统必须处理这些问题。但完善容错机制工作量巨大,要仔细考虑所有可能出错的事情,并充分测试。十年来,事务一直是简化这些问题的首选机制。事务将应用程序的多个读、写操作组合成一个逻辑单元。即事务中的读、写操作是个执行的整体:整个事务要么成功(提交),要么失败(中止或回滚)。若失败,程序可安全地重试。如此,便无需再担心部分失败的情况,应用层的错误处理就简单很多。也许你觉得事务就这么简单了,但细究起来也许不止于此。事务不是先天存在的;它是为简化应用层的编程模型而人为创造的。通过事务,应用程序可忽略某些潜在的错误和复杂的并发问题,因为DB会替应用处理好(称之为安全保证,safety guarantees)。并非所有应用都需要事务,有时可弱化事务处理或完全放弃事务(如为获得更高性能或更高可用性)。一些安全相关属性也可能会避免引入事务。如何判断是否需要事务?先要确切理解事务能为我们提供什么安全保障及其代价。本文将研究许多出错案例,并探索DB防范这些问题的算法和设计。尤其是并发控制领域,深入讨论各种竞争条件及DB的隔离级别。本文同时适用于单机DB与分布式DB。1 深入理解事务目前几乎所有关系型DB和一些非关系DB都支持事务。大多遵循IBM System R(第一个SQL数据库)在1975年的设计。50年来,尽管一些细节实现变化,但总体思路大同小异。MySQL、PostgreSQL、Oracle 和 SQL Server 等DB中的事务支持与 System R 极为相似。2000年后,NoSQL普及,目标在关系DB现状上,通过提供新数据模型和内置的复制和分区改进传统的关系模型。然而,事务成了这变革的受害者:新一代DB完全放弃事务或重新定义,即替换为比以前弱得多的保证。随新型分布式DB炒作,人们普遍认为事务是可扩展性的对立面,大型系统都必须放弃事务以获得更高性能和高可用性。但另一方面,还有一些DB厂商坚称事务是 “关键应用” 和 “高价值数据” 所必备的重要功能。这两种观点都有些夸张。事务有其优势和局限性。为理解事务权衡,来看看正常运行和各种极端case,看看事务到底能给我们什么。1.1 ACID到底意味着什么事务所提供的安全保证即ACID:原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)它由 TheoHärder 和 Andreas Reuter 于 1983 年为精确描述DB的容错机制。但实际上不同DB的 ACID 实现不尽相同。仅隔离性含义就有很多争议。当一个系统声称自己 “兼容ACID” 时,实际上能提供什么保证并不清楚。ACID现在几乎已经变成一个营销术语。不符合ACID的系统有时被称为BASE:基本可用性(Basically Available)软状态(Soft State)最终一致性(Eventual consistency)听起来比 ACID 还含糊不清,BASE唯一能确定的是 “它不是 ACID”,此外没有承诺任何东西。1.1.1 原子性-Actomicity事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。这个术语在计算机不同领域意味着相似但却微妙的差异。多线程编程中,若某线程执行一个原子操作,这意味着其它线程无法看到该操作的中间结果。系统只能处于操作前或操作后的状态,而非两者之间状态。而ACID的原子性并并不关系到多个操作的并发。它并未描述多个线程试图同时访问相同的数据会怎样,后者其实由ACID的隔离性所定义。ACID原子性其实描述客户端发起一个包含多个写操作的请求时可能发生的情况。如在完成部分写入后,系统就发生诸如进程崩溃,网络中断,磁盘变满或违反某种完整性约束。把多个写操作纳入到一个原子事务,万一出现这些故障而导致无法完成最终提交,则事务会中止,且DB须丢弃或撤销那些局部完成的更改。若无原子性,当多个更新操作中间发生错误,就得知道哪些更改已生效,哪些未生效,这寻找过程会很麻烦。或许应用程序可以重试,但情况类似,并且可能导致重复更新或错误的结果。原子性大大简化了这个问题:若事务已中止,应用程序可确定它没有改变任何东西,所以应用能安全重试。因此,ACID的原子性的定义特征:出错时中止事务,并将部分完成的写入全部丢弃。 或许 可中止性(abortability)是更恰当的术语。1.1.2 一致性在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持完整性。事务结束时,所有内部数据结构(如B树索引或双向链表)也都必须正确。一致性在不同场景有着不同含义:副本一致性及异步复制模型,引出最终一致性问题一致性哈希,是某些系统用于动态分区再平衡的一种策略CAP定理中,一致性一词用于表示线性化ACID中,一致性指DB在处于应用程序期待的“预期状态”ACID一致性主要是对数据有特定的预期状态,任何数据更改必须满足这些状态约束(或恒等条件)。例账单系统中,所有账户必须借贷相抵。若某事务从一个有效的状态开始,且事务处理期间任何写操作都没有违背约束,则最后结果依然符合有效状态。这种一致性本质要求应用层来维护状态一致,应用程序负责正确定义事务来保持一致性。这不是DB能保证的:即若你提供的数据违背恒等条件,DB也很难检测进而阻止该操作。DB 能完成针对某些特定类型的恒等约束检查,如外键约束或唯一性约束。但主要还是靠应用程序定义数据的有效/无效状态,DB 主要还是负责存储。原子性,隔离性和持久性是DB 本身属性,而ACID的一致性更多是应用层的属性。应用可能借助DB的原子性和隔离属性来达到一致性,但一致性本身并不源于DB。因此,字母C其实不应属于ACID 1。1.1.3 隔离性 Isolation一个事务所做的修改在最终提交前对其他事务不可见。大多DB都支持同时被多个客户端访问。若读、写的是不同数据,肯定没问题,但若访问相同记录,则可能会遇到并发问题。图-1的简单案例,假设两个客户端同时增加DB中的一个计数器。这里假设DB不支持自增。每个客户端先读取当前值,加1 ,再写回新值。两次增长,计数器应从42增至44,但由于竞态条件,最终结果是43 。ACID的隔离性意味着并发执行的多个事务相互隔离:互不交叉。传统DB教科书将隔离性定义为串行化,这意味着可以假装它是DB上运行的唯一事务。虽然实际上它们可能同时运行,但DB系统要确保当事务提交时,其结果与串行执行完全相同。然而实践中,由于性能问题,很少使用串行化的隔离。Oracle 11甚至不实现它,Oracle虽有个名为 “可串行的” 隔离级别,但本质上实现的快照隔离,提供了比串行化更弱的保证。1.1.4 持久性 Durability一旦事务提交,它对于数据的修改会持久化到DBDB系统本质是提供一个安全可靠的地方存储数据,而不用担心丢失。持久性就是这样的承诺,保证一旦事务提交成功,即使发生硬件故障或DB崩溃,事务写入的任何数据也不会丢失。单节点DB,持久性意味着数据已被写入非易失性存储设备,如硬盘、SSD。写入过程中,通常涉及预写日志,以便在磁盘数据损坏时可进行恢复。支持复制的DB中,持久性意味着数据已成功复制到多个节点。为实现持久性保证,DB必须等到这些写入或复制完成后,才能报告事务成功提交。完美的持久性是不存在的:若所有硬盘和所有备份同时被(人为)销毁,那DB也无能为力。没有技术能提供绝对的持久性保证。只有各种降低风险的技术,包括写盘,复制到远程机器和备份。1.2 单对象和多对象操作ACID的原子性和隔离性主要针对客户端在同一事务中包含多个写时,DB提供的保证:原子性若一系列写操作中间出错,则事务必须中止,并丢弃当前事务的所有写入。即DB免去了用户对部分失败的担忧,要么全部成功,要么全部失败的保证。隔离性同时运行的事务互不干扰。如若一个事务进行多次写入,则另一个事务要么看到其全部写入结果或什么都看不到,而不该是中间的部分结果。这些定义假设一个事务中修改多个对象(如行,文档,记录)。这种多对象事务目的通常是为了在多个数据对象之间保持同步。图-2展示一个电邮案例。显示用户未读件数:SELECT COUNT (*) FROM emails WHERE recipient_id = 2 AND unread_flag = true但若邮件太多,查询太慢,决定用单独字段存储未读数量。每当收到一个新邮件,增加未读计数器,当邮件标记为已读,也得减少该计数器。用户2遇到异常情况:邮件列表显示了未读消息,但计数器显示为零未读消息,因为还没更新 2。隔离性将保证用户2要么同时看到新邮件和增长后的计数器,要么都看不到,而不是前后矛盾的中间结果。图-3说明了对原子性需求:若事务过程中出错,导致邮箱和未读计数器的内容不同步,则事务将被中止,事务将被中止,且之前插入的电子邮件将被回滚。1.2.1 单对象写入原子性和隔离性也适用单个对象更新。如若向DB写入20KB的JSON文档:若发送第一个10KB后网络连接中断,DB是否只存储了无法完整解析的10KB JSON片段呢?若DB正在覆盖磁盘上的前一个值的过程中电源发生故障,最终是否导致新旧值混杂若另一个客户端在写入过程中读取该文档,是否会看到部分更新的内容这些问题很让人头大,故存储引擎必备设计:对单节点、单个对象层面上提供原子性和隔离性(如 KV 对)。原子性可以通过使用日志来实现崩溃恢复(B+树),并对每个对象加锁实现隔离 。某DB也提供高级原子操作 4,如自增,这就不再需要像图-1那样执行读取 - 修改 - 写回。类似的CAS操作,即只有当前值未被其他并发修改过,才允许执行写。这些单对象操作可有效防止多个客户端并发修改同一对象时的丢失更新。但它们不是通常意义上的事务。虽然CAS及其他单一对象操作有时被称为 “轻量级事务”,甚至出于营销目的被称为 “ACID”,但存在误导。事务通常针对的是多个对象,将多个操作聚合为一个执行单元的机制。1.2.2 多对象事务的必要性许多分布式数据存储不支持多对象事务,因为多对象事务很难跨分区实现,且在高可用性或高性能情况下也碍事。但分布式数据库中实现事务,并没有什么原理障碍。但是否需要多对象事务?是否可能只用KV数据模型和单对象操作就能满足应用需求呢?确有一些场景,单对象插入、更新和删除就够了。但很多其他场景要求协调写入几个不同的对象:关系数据模型中,表中的某行可能是另一个表中的外键。类似的,图数据模型中,顶点有着到其他顶点的多个边。多对象事务用以确保这些外键引用始终有效:当插入几个相互引用的记录时,保证外键总是正确、最新,否则数据更新就毫无意义。文档数据模型,若待更新的字段都在同一文档,则可视为单个对象,此时无需多对象事务。但缺join功能的文档DB会鼓励非规范化。当更新这种非规范化数据时,如图-2,就需一次更新多个文档。事务就能有效防止非规范化数据出现不同步带有二级索引的DB(除了纯粹KV存储系统以外几乎都有),每次更改值时都需同步更新索引。事务角度,这些索引是不同的DB对象:如若无事务隔离,记录可能出现在一个索引中,但没有出现在另一个索引中,因为第二个索引的更新还没发生这些应用即使没有事务支持,或许仍可工作。但无原子性保证,错误处理就复杂多了,缺乏隔离性,就会导致并发问题。1.2.3 处理错误和中止事务的一大关键特性,若出错,中止所有操作,之后可安全重试。ACID DB基于此理念:若DB存在违反原子性、隔离性或持久性的风险,则完全放弃事务,而非部分放弃。但并非所有系统都遵循这理念。如无主节点复制的数据存储会在 “尽力而为” 基础上尝试多做点。可概括理解为为:DB已尽其所能,但万一遇到错误,系统不会撤销已完成的操作,此时需应用程序责任从错误中恢复。错误无法避免,但我们倾向于只考虑正常case,而忽略错误处理。如Rails ActiveRecord和 Django这类ORM框架,事务异常时不会重试而只是简单抛堆栈信息,用户虽然得到错误提示,但所有之前的输入都被丢弃了。这肯定不该发生,中止的重点就是允许安全重试。重试中止的事务虽是个简单有效的错误处理机制,但不完美:若事务实际已执行成功,但返回给客户端的消息在网络传输时故障(所以对客户端来说,事务是失败的),则重试就会导致重复执行,此时需额外的应用层级去重机制若错误由高负载导致,则重试事务将更糟。可设置重试次数阈值,如指数回退,并处理过载问题临时性故障(如死锁,网络中断和节点故障切换)导致的错误需要重试。但发生个永久性故障(如违反约束),则重试毫无意义若事务在DB之外也有副作用,即使事务被中止,也可能发生这些副作用。如发送电子邮件,那你肯定不希望每次重试都重发。若想确保多个不同系统同时提交或放弃,考虑两阶段提交若客户端进程在重试中也失效,没有其他人能继续负责重试,则那些写入数据都将丢失乔・海勒斯坦(Joe Hellerstein)指出,在 Härder 与 Reuter 的论文中,“ACID 中的 C” 是被 “扔进去凑缩写单词的”【7】,而且那时候大家都不怎么在乎一致性。 ↩︎可以说邮件应用中的错误计数器并不是什么特别重要的问题。但换种方式来看,你可以把未读计数器换成客户账户余额,把邮件收发看成支付交易。 ↩︎这并不完美。若TCP连接中断,则事务必须中止。假定中断发生在客户端请求提交之后,但在服务器确认提交完成前,则客户端最后并不知道事务是否已完成提交。为解决该问题,事务管理器可定义一个唯一的事务标识符来逻辑上绑定一组写操作,且该事务标识符独立于TCP连接。 ↩︎严格地说,原子自增(atomic increment) 这个术语在多线程编程的意义上使用了原子这个词。ACID下应该称为 隔离的(isolated) 的或 可串行的(serializable) 的增量。 ↩︎
0
0
0
浏览量2023
攻城狮小明

精通Java事务编程(2)-弱隔离级别之已提交读

若两个事务不触及相同数据,即无数据依赖关系,则它们能安全并行运行。只有当:某事务读取由另一个事务同时修改的数据时或两个事务同时修改相同数据才会出现并发问题。并发 BUG 很难通过测试找到,因为这样的错误只有在特殊时序下才会触发。这样的时序问题可能非常少发生,通常很难重现 1。并发性也很难推理,特别是在大型应用中,你不一定知道哪些其他代码正在访问DB。只有一个用户访问数据时,应用开发就够麻烦了,多用户并发更困难,每个数据都可能被多个用户修改。因此,DB一直试图通过【事务隔离】来隐藏内部的各种并发问题。理论上,隔离是假装没有并发发生,让程序员生活不再加班。而可串行化隔离级别就是DB保证事务最终效果如同串行执行。但可串行化会有极大性能损失,许多DB不愿意牺牲性能,所以倾向较弱隔离级别,防止某些而非全部并发问题。弱隔离导致的并发性错误不仅是理论问题,它们已造成很多资损,审计调查和客户数据破坏。比起盲目依赖工具,不如对各种并发问题及如何防止有深入理解,构建可靠、正确的应用。2.1 读已提交(Read Committed)最基本的事务隔离级别2,提供如下保证:读DB时,只能看到已成功提交的数据(防止脏读)写DB时,只会覆盖已成功写入的数据(防止脏写)2.1.1 防止脏读某事务已完成部分数据写,但事务尚未提交或中止。另一个事务可以看到尚未提交的数据吗?是,则为脏读。防止脏读的意义若事务需更新多个对象,脏读代表另一个事务可能只看到部分更新。如图-2,用户看到新的未读邮件,但看不到更新的计数器。这就是电邮脏读。看到部分更新的数据会让用户困惑若事务中止,则所有写都得回滚(如图-3)。若发生脏读,意味着一个事务可能看到稍后需回滚的数据,即从未实际提交给DB的数据。2.1.2 防止脏写若两个事务同时尝试更新DB的相同对象,不知道写的顺序如何,但通常认为后写入会覆盖前写入。若事务需更新多个对象,如图-5的二手车销售网站,Alice 和 Bob 同时购买同一辆车。购买汽车需两次DB写入:网站上的商品列表需更新,以反映买家购买,销售发票需发给买家。图-5的销售属于 Bob(因为他成功更新车辆列表),但发票却寄给了爱丽丝(因为她成功地先更新了发票表)。RC就能避免此类事故。但RC不能防止图-1的计数器增量竞争。它的第二次写入确实发生在第一个事务提交后,所以不是脏写,但结果仍不正确。防止更新丢失中将讨论如何修正2.1.3 实现原理互联网主流隔离级别,Oracle 11g、PostgreSQL、SQL Server 2012、MemSQL和其他许多DB的默认设置。2.1.3.1 防脏写DB一般通过 行锁(row-level lock)防脏写:当事务想修改某对象(如行或文档),必须首先获得该对象的锁。然后一直持有直到事务提交(或中止)。一次只有一个事务可持有特定对象的锁;若另一事务要更新同一对象,则必须等到前面事务提交或中止后,才能获取锁并继续。这是RC模式(或更高隔离级别)的DB自动完成的锁定。2.1.3.2 防脏读① 方案一因此,大多DB 3 使用图-4方案防脏读:对于写入的每个对象,数据库都会记住旧的已提交值,和由当前持有写入锁的事务设置的新值。当事务正在进行时,任何其他读取对象的事务都会拿到旧值。 只有当新值提交后,事务才会切换到读取新值。
0
0
0
浏览量2014
攻城狮小明

精通Java事务编程(4)-弱隔离级别之防止更新丢失

RC和快照隔离级别主要都是为解决 只读事务遇到并发写时可以看到什么(虽然中间也涉及脏写),还没触及另一种情况:两个写事务并发,而脏写只是写并发的特例。写事务并发带来最着名的问题就是丢失更新,如图-1的两个并发计数器增量为例。应用从DB读一些值,修改它并写回修改后的值,则可能导致丢失更新。若两事务同时执行,则其中一个的修改可能丢失,因为第二个写内容并未包括第一个事务的修改(有时会说后面的写入 狠揍(clobber) 了前面的写入)这种模式发生在各种不同场景:增加计数器或更新账户余额(需要读取当前值,计算新值并写回更新后的值)在复杂值中进行本地修改:例如,将元素添加到 JSON 文档中的一个列表(需要解析文档,进行更改并写回修改的文档)两个用户同时编辑 wiki 页面,每个用户通过将整个页面内容发送到服务器来保存其更改,覆写数据库中当前的任何内容。这是一个普遍的问题,所以已经开发了各种解决方案。2.3.1 原子写许多DB支持原子更新,避免了在应用程序代码中执行读取 - 修改 - 写入。用这些操作通常是最好的解决方案。如下指令在大多数关系DB中并发安全:UPDATE counters SET value = value + 1 WHERE key = 'foo';类似像:MongoDB文档DB提供了对 JSON 文档的一部分进行本地修改的原子操作Redis支持修改数据结构(如优先级队列)的原子操作并不是所有的写操作都可以用原子操作的方式来表达,例如维基页面的更新涉及到任意文本编辑 1,但是在可以使用原子操作的情况下,它们通常是最好的选择。实现方案一般采用对读取对象加排它锁来实现,以便在更新完成之前没有其他事务可以读它。这种技术有时被称为游标稳定性(cursor stability)另一个实现方案是强制所有的原子操作在单线程执行。但ORM框架很容易导致执行不安全的读取 - 修改 - 写入,而不是使用数据库提供的原子操作。若你知道自己在做什么,或许这不会引发什么问题,但往往会埋下潜在Bug。2.3.2 显式加锁若DB不支持内置原子操作,防止丢失更新的另一个选择是让应用程序显式锁定待更新对象。然后应用程序执行读取 - 修改 - 写入,此时若其他事务尝试同时读取对象,则必须等待,直到第一个 读取 - 修改 - 写入 完成。如多人游戏,其中几个玩家能同时移动同一个数字。只靠原子操作可能不够,因为应用程序还需确保玩家的移动符合规则,这可能涉及一些应用层逻辑,不可能将其剥离转移给DB层在查询时执行。此时,可使用锁来防止两名玩家同时移动相同棋子,如例-1:例-1 显式锁定行,以防止丢失更新BEGIN TRANSACTION; SELECT * FROM figures WHERE name = 'robot' AND game_id = 222 # 指示DB对返回的所有结果行要加锁。 FOR UPDATE; -- 检查玩家的操作是否有效,然后更新先前 SELECT 返回棋子的位置 UPDATE figures SET position = 'c4' WHERE id = 1234; COMMIT;这有效,但要做对,需仔细考虑应用层逻辑。忘记在代码某处加锁很容易引入竞争条件。2.3.3 自动检测更新丢失原子操作和锁是通过强制 读取 - 修改 - 写入 串行执行来避免丢失更新。另一种方法是允许它们并发,但若事务管理器检测到丢失更新,则中止当前事务,并强制它们回退到安全的 读取 - 修改 - 写入。该方案的一个优点是DB能结合快照隔离高效执行检查。PostgreSQL的可重复读,Oracle的可串行化和 SQL Server 的快照隔离级别,都能自动检测到丢失更新,并中止违规的事务。但MySQL/InnoDB的可重复读并不会检测丢失更新。一些作者认为,DB必须防止丢失更新,才称得上是提供了快照隔离,所以在这种定义下,MySQL属于没有安全支持快照级别隔离。丢失更新检测是个好功能,应用代码因此不依赖某些特殊的DB功能。你可能忘记使用锁或原子操作,但丢失更新的检测会自动生效,就不太容易出错。2.3.4 CAS不提供事务的DB有时支持CAS,可避免丢失更新:只有当前值从上次读取时一直未改变,才允许更新发生。若当前值与先前读取的值不匹配,则更新不起作用,就重试读取 - 修改 - 写入。如为防止两个用户同时更新同一个 wiki,可尝试如下操作,只有当页面从上次读取之后没发生变化时,才会执行当前的更新:-- 根据数据库的实现情况,这可能安全也可能不安全 UPDATE wiki_pages SET content = 'new content' WHERE id = 1234 AND content = 'old content';若内容已更改且不再与 “旧内容” 匹配,则更新失败,需应用层再次检查更新是否生效,必要时重试。若WHERE语句运行在DB的某个旧快照,即使另一个并发写入正在运行,条件可能仍为真,最终可能无法防止更新丢失。所以在使用前,应先仔细检查“比较-设置”操作的安全运行条件。2.3.5 冲突解决和复制支持多副本的数据库中,防止丢失更新还需考虑:由于多节点上存在数据副本,不同节点可能并发修改数据,需采取额外措施防止丢失更新。加锁、CAS前提都要求只有一个最新的数据副本。但多主或无主复制的多副本DB,通常允许多个并发写,并异步复制到副本,所以会出现多个最新的数据副本。此时加锁或CAS将不再适用。正如系列文章(5)中的【检测并发写入】一节所述,多副本DB通常允许并发写入创建多个冲突版本的值(互称为兄弟),并使用应用层代码或特殊数据结构来解决、合并这些多版本。若操作可交换(顺序无关,在不同副本上以不同顺序执行时,仍得到相同结果),则原子操作在多副本情况下也能工作。如递增计数器或向集合添加元素都是典型的可交换操作。这是 Riak 2.0 新数据类型思想,当一个值被不同客户端同时更新时, Riak自动将更新合并在一起,避免发生更新丢失。而最后写入胜利(LWW)的冲突解决方法则容易丢失更新,不幸的是,LWW目前是许多多副本DB的默认配置。1.将文本文档的编辑表示为原子的变化流是可能的,尽管相当复杂。请参阅 “自动冲突解决”。 ↩︎
0
0
0
浏览量2012
攻城狮小明

精通Java事务编程(3)-弱隔离级别之快照隔离和可重复读

表面看,RC已满足事务所需的一切特征:支持中止(原子性),防止读取不完整的事务结果,并防止并发写的混乱。这点很关键!为我们的开发省去一大堆麻烦。但此隔离级别仍有很多地方可能产生并发错误。如图-6说明RC可能发生的问题。Alice在银行有1000存款,分为两个账户,每个500。现有一笔转账交易从账户1转移100到账户2。若她在提交转账请求后、银行DB系统执行转账的过程中间,查看两个账户的余额,她可能看到账号2在收到转账前的余额(500),和账户1在完成转账之后的余额(400)。对Alice,貌似她的账户总共只有900,100消失!这种异常就是不可重复读(nonrepeatable read)或读倾斜(read skew):若Alice在交易结束时再读取账户1的余额,将看到和她之前的查询看到的不同的值(600)。RC下,不可重复读被认为是可接受的:Alice 看到的帐户余额的确都是账户当时的最新值。Alice案例不是长期持续的问题,几s后当她刷新银行页面,可能就看到一致的帐户余额。但有的场景不能容忍这种暂时的不一致:备份备份需复制整个DB,大型DB可能需数h。备份进程运行时,DB仍会接受写。因此镜像备份里可能包含一些旧版本数据和一些新版本数据。从这样的备份中恢复,最终就会导致永久性的不一致(如那些消失的存款)分析查询和完整性检查有时查询会扫描几乎大半个DB。这类查询在分析中很常见,也可能是定期的数据完整性检查(监视数据损坏情况)。若这些查询在不同时间点观察DB,则可能会返回无意义的结果【快照隔离】是这类问题最常见解决方案。每个事务都从DB的一致性快照(consistent snapshot)中读取,即事务一开始所看到是最近提交的数据。即使这些数据随后被另一个事务更改,每个事务也只能看到该特定时间点的旧数据。快照隔离对长时间运行的只读查询(如备份和分析)很有用。若数据在查询执行的同时变化,则很难理解查询结果的物理含义。而若查询的是DB在某特定时间点冻结时的一致性快照,则查询结果含义明确。快照隔离很流行:PostgreSQL、InnoDB引擎的MySQL、Oracle、SQL Server 等都支持。实现快照隔离类似RC,快照隔离的实现通常使用写锁防止脏写,正在进行写入的事务会阻止另一个事务修改同一个对象。但读取则不无需加锁。性能角度,快照隔离的关键点:读不会阻塞写,写不会阻塞读。这允许DB可在正常处理写入的同时,在一致性快照上执行长时间的只读查询,且两者之间没有任何锁竞争。为实现快照隔离,DB用类似图-4防脏读但却更通用的机制。考虑到多个正在进行的事务可能在不同时间点查看数据库状态,所以DB保留对象的多个不同的提交版本,所以这种技术也称为多版本并发控制(MVCC, multi-version concurrency control)。若只是为提供RC,而非完整的快照隔离,则只保留对象的两个版本即可:已提交的旧版本尚未提交的新版本所以,支持快照隔离的存储引擎一般也直接使用MVCC实现RC。典型做法:在RC下,为每个不同的查询单独创建一个快照而快照隔离则是对整个事务使用相同的一个快照。一致性快照的可见性规则当事务读DB时,通过事务ID可决定哪些对象可见,哪些不可见。要想对上层应用维护好快照的一致性,需仔细定义可见性规则:每个事务开始时,DB列出当时所有当时还在进行中(即尚未提交或中止)的其它事务,然后忽略这些事务完成的部分写入(尽管之后可能会被提交),即不可见所有中止事务所做的任何修改全部不可见较晚事务ID(即晚于当前事务开始)所做的任何修改不可见,而不管这些事务是否已完成提交此外的所有其他写入都对应用查询可见以上规则适用于创建、删除操作。图-7中,当事务12从账户2读时,会看到500余额,因为500余额的删除是由事务13完成的(根据规则 3,事务12看不到事务13执行的删除),同理400美元记录的创建也不可见。即若如下两个条件都成立,则该数据对象对事务可见:读事务开始的时刻,创建该对象的事务已完成提交对象未被标记为删除或即使被标记为删除了,但删除事务在当前读事务开始时还没有完成提交长时间运行的事务可能会使用快照很长时间,其他事务角度,它可能在持续访问正在被覆盖或删除的内容。由于没有就地更新,而是每次修改总创建一个新版本,因此DB可以以较小运行代价来维护一致性快照。索引和快照隔离多版本DB如何支持索引?一种方案是索引直接指向对象所有版本,并且需要索引查询过滤掉对当前事务不可见的对象版本。当后台的GC进程决定删除某个事务不可见的旧对象版本时,相应索引条目也随之删除。实践中,许多细节决定了多版本并发控制的性能,如:可将同一对象的不同版本放入同一内存页,PostgreSQL如此优化可避免更新索引CouchDB、Datomic 和 LMDB使用另一种方案。虽然也使用B树,但采用追加/写时复制(append-only/copy-on-write),当需要更新时,不会修改现有的页,而总是创建一个新的修改副本,拷贝必要的内容,然后让父结点或递归向上直到树root都指向新创建的结点。那些不受更新影响的页面都无需复制,保持不变并被父结点所指向。这种使用追加的B树,每个写入事务(或一批事务)都会创建一个新的B 树,当创建时,从该特定树根生长的树就是该时刻DB的一致性快照。这时就没必要根据事务ID再去过滤对象,每个写入都会修改现有的B树,因为之后的 询可以直接作用于特定快照B-tree(有利于查询性能)。采用这种方案依然需要后台进程来执行压缩和GC。可重复读与命名混淆快照隔离对只读事务特别有效。但DB实现用不同名字来称呼:Oracle 中称为可串行化(Serializable)PostgreSQL 和 MySQL 中称为可重复读(repeatable read)命名混淆原因是SQL标准未定义快照隔离,而仍是基于System R 1975年定义的隔离级别,那时还没快照隔离。而定义了 可重复读,表面看起来接近快照隔离。 所以PostgreSQL 和 MySQL 称快照隔离级别为可重复读(repeatable read),这符合标准要求。但SQL标准对隔离级别的定义存在缺陷的,模糊,不精确,做不到独立于实现。有几个DB实现了可重复读,但它们实际提供的保证差异很大。IBM DB2 使用 “可重复读” 实现可串行化级别的隔离。所以导致结果,无人真正知道可重复读到底啥意思。事务ID是32位整数,所以大约在40亿次事务后溢出。 PostgreSQL 的 Vacuum 过程会清理老旧的事务 ID,确保事务 ID 溢出(回卷)不会影响到数据。 ↩︎
0
0
0
浏览量2011
攻城狮小明

精通Java事务编程(7)-可串行化隔离级别之两阶段锁定(2PL,two-phase locking

近30年,DB只有一种广泛使用的串行化算法:两阶段加锁 1之前我们知道,加锁可防止脏写:即若两个事务同时尝试写入同一对象,则锁可确保第二个写必须等第一个写完成事务(中止或提交)才能继续。两阶段锁定类似,但锁的强制性更高。只要没有写入,就允许多个事务同时读取同一个对象。但对象只要有写,就得加锁独占访问:若事务 A 已读某对象,此时B想写该对象,则必须等A提交或中止才能继续,这确保 B 不能在 A 执行过程的中间意外改变对象若事务 A 已写某对象,此时 B 想读该对象,则 B 必须等 A 提交或中止才能继续,像图-1读取旧版本的对象在 2PL 下不可接受2PL不仅在并发写互斥,读写之间也互斥。快照级别隔离是读写不互斥,这是 2PL 和快照隔离的关键区别。且因 2PL 提供串行化,可防止前文讨论的所有竞争条件,包括丢失更新和写倾斜。3.2.1 实现原理2PL已在:MySQL(InnoDB)和 SQL Server 实现可串行化DB2 中的可重复读读与写的阻塞是通过为数据库中每个对象添加锁来实现的。锁可处于 共享模式或独占模式,使用如下:若事务要读对象,则须先共享模式获取锁。允许多事务同时持有一个对象的共享锁。但若某事务已持有对象的独占锁,则其它事务必须等待若事务要写对象,须以独占模式获取锁。禁止其他事务同时持有锁(无论共享模式or独占模式),即若对象上存在锁,则写事务必须等待若事务先读再写对象,则需将其共享锁升级为独占锁。升级锁的流程和直接获得独占锁相同事务获得锁后,必须一直持有锁直到事务结束。这就是 “两阶段” 名字来源:第一阶段(当事务正在执行时)获取锁,第二阶段(在事务结束时)释放所有的锁。由于使用了这么多锁,很容易死锁:如事务A等待B释放锁,而B等A释放锁。DB会自动检测事务之间死锁,并强行中止一个。被中止的事务需由应用层重试。3.2.2 性能其巨大缺点及1970s以来没有被广泛使用的原因还是其性能:事务吞吐量和查询响应时间比弱隔离级别下差太多。部分因为获取、释放锁开销,但更重要是并发性降低。按设计,若两个并发事务试图做任何可能导致竞争条件的事情,则其一必须等待另一完成。传统关系DB不限制事务的执行时间,因为它们是为等待人类输入的交互式应用而设计。结合2PL,最终结果是,当一个事务还需等待另一事务时,则最终等待时间几乎无上限。即使能保证所有事务都很短,若有多事务同时访问同一对象,会形成一个等待队列,事务需要等待前面事务完成后才能继续。因此,2PL DB的访问延迟具有极大不确定性,若负载存在严重竞争,以百分比方式观察延迟指标会发现很缓慢。可能某缓慢事务或一个访问大量数据并获取许多锁的事务,就能把系统其他部分拖慢。当需要稳定操作时,这种不稳定性是致命的。基于锁实现的RX也可能死锁,但 2PL 下取决于事务的访问模式,死锁更频繁。这可能是一个额外的性能问题:当事务由于死锁而被中止并被重试时,应用层就需从头重试。若死锁频繁,则最后性能和效率必然大打折扣。谓词锁对加锁,忽略了一个微妙但重要的细节。在写倾斜幻读中的幻读问题,即一个事务改变另一个事务的查询结果。可串行化隔离也必须防止幻读。会议室预订案例,若事务在查询某时间段内一个房间的预订情况,则另一个事务不能同时插入或更新同一时间段内该房间的预订 (可同时插入其他房间的预订或在不影响另一个预定的条件下预定同一房间的其他时间段)。要实现就需要谓词锁(predicate lock),类似共享/独占锁,但不属于特定对象(如表的某行),而是作用于所有符合某些搜索条件的对象,如:SELECT * FROM bookings WHERE room_id = 123 AND end_time > '2018-01-01 12:00' AND start_time < '2018-01-01 13:00';谓词锁会限制如下访问:若事务A想读取某些满足匹配条件的对象,如SELECT 查询,必须获取查询条件上的 共享谓词锁(shared-mode predicate lock)。若事务B持有任何满足这一查询条件对象的独占锁,则A必须等到B释放锁后才能继续执行查询若事务A想插入、更新或删除任何对象,须先检查所有旧值或新值是否和现有谓词锁匹配。若B持有匹配的谓词锁,则A须等B完成提交或中止后才能继续关键在于,谓词锁甚至适用于数据库中尚不存在,但将来可能会添加的对象(幻象)。如果两阶段锁定包含谓词锁,则数据库将阻止所有形式的写入偏差和其他竞争条件,因此其隔离实现了可串行化。索引范围锁但谓词锁性能不佳:若活跃事务持有很多锁,则检查匹配的锁很耗时。因此,大多2PL DB实际上实现的是索引范围锁(index-range locking,也称为 next-key locking),本质是对谓词锁的简化或近似。简化谓词锁的方式是扩大其保护的对象,这肯定是安全的。如若你有12:00~13:00预订 123 号房间的谓词锁,则锁定123号房间的所有时间段或锁定12:00~13:00时间段的所有房间就是安全的近似。这样,任何与原始谓词锁冲突的操作肯定也和近似后的区间锁相冲突。房间预订DB,一般在:room_id 列建索引并/或在 start_time 和 end_time 上有索引否则前面的查询在大型DB上的速度会很慢。假设索引位于 room_id 上,并且数据库使用此索引查找 123 号房间的现有预订。现在数据库可以简单地将共享锁附加到这个索引项上,指示事务已搜索 123 号房间用于预订。或者,若DB使用基于时间的索引来查找预订,则可将共享锁附加到该索引中的一系列值,指示事务已搜索了该时间段内的所有值 (如直到2023年 1 月 1日)无论哪种,查询条件的近似值都附加到某个索引上。若另一事务想插入、更新或删除同一房间和/或重叠时间段的预订,则须更新这些索引的相同部分,就一定会和共享锁冲突,将被迫等到共享锁被释放。这有效防止了幻读和写倾斜。索引范围锁并不像谓词锁精确(会锁定更大范围的对象,超出维持可串行化所必需的范围),但由于开销低得多,是很好的折衷方案。若无可挂载范围锁的索引,则DB可退化到使用整表的共享锁。这对性能不利,会阻止所有其他事务的写,但这是一个安全的回退位置。有时称为 严格两阶段锁定(SS2PL, strong strict two-phase locking),以便和其他 2PL 变体区分。 ↩︎
0
0
0
浏览量2008
攻城狮小明

精通Java事务编程

精通Java事务编程
0
0
0
浏览量2148
攻城狮小明

Stata无偏性检验

无偏性检验原理Stata回归的系数值只是某个样本的观测值,不一定是真值(事实上恰好等于真值的概率极小),但较大概率会落在真值的附近,且分布有一定规律。对于回归系数结果的无偏性,我们可以通过在整体中反复抽样进行检验,观测结果分布。检验思路在Excel中随机生成一列x(自变量),一列u(干扰项),然后令y=1.6x+u,这样生成x,y两列相关性很高但并非完全线性的数据,其中u刻画了其他因素的冲击。Stata代码此检验需要做多次循环,而恰好Stata写循环又比较鬼畜,下面给出完整代码://随机指定treat变量 forvalue i=1/200{ //循环200次 clear import excel "C:\Users\Desktop\计量助教\有效性检验\有效性检验.xlsx", firstrow //调入数据,换path gen random_digit1=ceil(runiform(1,1000)) //生成1到1000随机数(可能重复) g new_treat=0 sort random_digit1 //根据随机数random_digit1排序 gen s=_n replace new_treat=1 if s<51 //样本容量50 /*(51-1)*/ //合并,回归,提取系数// //g x = post*new_treat reg y x if new_treat==1 //筛选lable为1的样本部分 g _b_new_treat = _b[x] //提取x的回归系数 g _se_new_treat = _se[x] //提取x的标准误 g _t_new_treat=_b_new_treat/_se_new_treat //计算得到p值 g _p_new_treat = 2*ttail(e(df_r), abs(_t_new_treat)) //计算得出pvalue keep _b_new_treat _se_new_treat _t_new_treat _p_new_treat duplicates drop _b_new_treat, force save placebo`i', replace //把第i次placebo检验的系数和标准误存起来 } //纵向合并200次的系数和标准误 use placebo1, clear forvalue i=2/200{ append using placebo`i' //纵向合并200次回归的系数及标准误 } //t值,p值作图 gen p值=_p_new_treat gen 估计系数=_b_new_treat twoway scatter p值 估计系数 , color(*.5)|| kdensity 估计系数 ,tline(1.6 , lp(dash) lc(black) ) tlabel(1.6 , add labsize(*.75)) //1.6是基准回归中的系数真值 * 删除临时文件 * forvalue i=1/200{ erase placebo`i'.dta } * 保存结果 * save result2.dta,replace代码注释已经较为详细,只要把数据路径换掉就可以跑。结果选取1000个观测值,第一次跑200遍,每次随机抽取50个观测值:下面还有每次抽取400个,跑50次循环结果:可以明显看出,样本容量较大时更容易发现真值,或者说从分布结果来看距离真值更近。
0
0
0
浏览量2008
攻城狮小明

Stata:工具变量法(2SLS)及其三种检验代码

工具变量(2SLS)介绍使用OLS还是工具变量?——豪斯曼检验H0:所有变量均为外生reg y x1 x2 estimates store ols ivregress 2sls y x1 (x2=z1 z2) 怀疑x2为内生变量 estimates store iv hausman iv ols,constant sigmamore对于DWH检验,可使用:estat endogenous2SLS的Stata命令ivregress 2sls depvar [varlist1] (varlist2=ivlist),r first其中depvar为被解释变量,[varlist1]为外生解释变量,varlis2为内生解释变量,ivlist为工具变量,添加r first代表显示第一阶段回归结果三大检验不可识别检验需下载ivreg2弱工具变量检验estat firststaga,all forcenonrobust过度识别检验estat overid
0
0
0
浏览量2007
攻城狮小明

Stata:工具变量法(两阶段最小二乘法2SLS)——解决模型内生性

计量良心OLS大法在解释变量与扰动项不相关时较为常用,一旦二者出现相关性往往无法解决,此时OLS估计可能不一致,问题产生原因可能是遗漏变量、联立偏差等。较为常见的解决方法是使用工具变量法。本文以y = a 0 + a 1 ∗ c + u i y=a0+a1*c+uiy=a0+a1∗c+ui为例,y yy为被解释变量,c cc为解释变量,但模型有内生性,此时选取工具变量为x xx。工具变量的选择首先工具变量的选择要满足两个条件:1.相关性:工具变量与内生解释变量相关,即C o v ( x , c ) ≠ 0 Cov(x,c)≠02.外生性:工具变量与u i uiui不相关,即C o v ( x , u i ) = 0 Cov(x,ui)=0Cov(x,ui)=0两阶段最小二乘法核心思路:c与ui相关,将c中与ui相关的部分分力出去,只留下与其不相关部分;其中转换工具被称为工具变量(IV)。第一阶段构造c = b 0 + b 1 ∗ x + v i c=b0+b1*x+vic=b0+b1∗x+vi,通过OLS估计的参数,拟合出c ^ \hat{c} c^ 。此时c与ui相关,c ^ \hat{c} c^与ui不相关第二阶段通过y与c ^ \hat{c} c^构模型y = b 0 + b 1 ∗ c ^ + e i y=b0+b1*\hat{c}+eiy=b0+b1∗ c^+ei,估计出b 1 , b 0 b1,b0b1,b0即为需求参数。相关检验首先对于选用OLS还是工具变量法,需要进行豪斯曼检验此方法中工具变量的选取最为关键,可能有三种情况:不可识别:需要工具变量个数<内生变量个数恰好识别:需要工具变量个数=内生变量个数过度识别:需要工具变量个数>内生变量个数其对应需要进行的检验为:不可识别检验弱工具变量检验过度识别检验
0
0
0
浏览量2008
攻城狮小明

Stata:时间序列中的格兰杰因果检验

时间序列 中Stata的格兰杰因果检验在实证分析中,因果关系经常需要判断。对此,Granger提出了一种解决方法:如果x是y的原因,且不存在反向因果,则x过去值可以预测y未来值,反之则不然。具体即建立时间序列的滞后回归模型,分析x的滞后项是否对y有明显的预测能力。此检验可分为四步操作:设置时间序列第一步,设置时间序列,注意此处若时间不连续会对后续滞后回归有影响。#设置时间序列 tsset var~结果大概这样. tsset trade_date time variable: trade_date, 03jan2018 to 27may2020, but with gaps delta: 1 day判断最佳滞后阶数第二步,判断最佳滞后阶数,此处可加判断条件取符合条件部分,至于判断取决于其AIC与BIC,一般AIC值即可判断(AIC越小越好)。#判断滞后阶数 varsoc var~1 var~2 (if 条件1)结果这样. varsoc log_return stk_log_return if time==0 Selection-order criteria Sample: 12jan2018 - 13mar2020, but with gaps Number of obs = 96 +---------------------------------------------------------------------------+ |lag | LL LR df p FPE AIC HQIC SBIC | |----+----------------------------------------------------------------------| | 0 | 680.432 2.5e-09 -14.134 -14.1124 -14.0806* | | 1 | 687.212 13.561 4 0.009 2.4e-09 -14.1919 -14.1271 -14.0317 | | 2 | 698.272 22.119* 4 0.000 2.0e-09* -14.339* -14.231* -14.0719 | | 3 | 701.008 5.4713 4 0.242 2.1e-09 -14.3127 -14.1615 -13.9387 | | 4 | 702.141 2.2668 4 0.687 2.2e-09 -14.2529 -14.0586 -13.7721 | +---------------------------------------------------------------------------+ Endogenous: log_return stk_log_return Exogenous: _cons 阶数判断有找标星星的就好了,这个就是两阶滞后较优。也可以一个个回归试一试,报告AIC和BIC比较:#回归后报告AIC BIC estat ic estat ic Akaike's information criterion and Bayesian information criterion ----------------------------------------------------------------------------- Model | N ll(null) ll(model) df AIC BIC -------------+--------------------------------------------------------------- . | 334 1080.45 1100.915 5 -2191.831 -2172.775 ----------------------------------------------------------------------------- Note: BIC uses N = number of observations. See [R] BIC note.上面两种方法均可,其中①中可以加条件筛选:方法②不能加if判断语句,其中的n即为第二部判断的滞后阶数。 reg log_return l.log_return l.stk_log_return l2.log_return l2.stk_log_return Source | SS df MS Number of obs = 334 -------------+---------------------------------- F(4, 329) = 10.72 Model | .003495038 4 .00087376 Prob > F = 0.0000 Residual | .026808292 329 .000081484 R-squared = 0.1153 -------------+---------------------------------- Adj R-squared = 0.1046 Total | .03030333 333 .000091001 Root MSE = .00903 -------------------------------------------------------------------------------- log_return | Coef. Std. Err. t P>|t| [95% Conf. Interval] ---------------+---------------------------------------------------------------- log_return | L1. | .1295246 .0483182 2.68 0.008 .0344729 .2245763 | stk_log_return | L1. | .0418571 .044596 0.94 0.349 -.0458722 .1295863 | log_return | L2. | -.3160259 .0569197 -5.55 0.000 -.4279985 -.2040534 | stk_log_return | L2. | .0892175 .0450037 1.98 0.048 .0006862 .1777488 | _cons | .0000102 .0004966 0.02 0.984 -.0009668 .0009871 -------------------------------------------------------------------------------- 我使用的数据为2018年1月3日到2020年5月27日的数据,有效数据条数为581,但此时显示: Number of obs = 334581条记录两阶滞后回归后只剩334个观测值?被吞了?注意,知识点来了,又一个小细节。并不是计算有问题,只是由于滞后阶数的设置为2,也就是说接下来两天 (注意!不是接下来两个交易日或者有观测数据的日期) 有数据才能计入有效进入回归。而我使用的数据是可转债和股票交易数据,周末没有交易,周五周四的滞后两天是周末,没有观测值就被跳过了,遇到节假日也是同理。也就是说非交易日前两天都没了,所以观测数比实际数据少了40%以上。如果想要对上一个交易日或者上一个观测进行回归,自行构造虚拟时间序列设置为第一步即可。报告检验结果第四步,对于使用第三步中方法①的可以使用test命令,对于使用第三步中方法②的可以使用#报告结果vargrangervargranger Granger causality Wald tests +------------------------------------------------------------------+ | Equation Excluded | chi2 df Prob > chi2 | |--------------------------------------+---------------------------| | log_return stk_log_return | 2.3602 1 0.124 | | log_return ALL | 2.3602 1 0.124 | |--------------------------------------+---------------------------| | stk_log_return log_return | .15784 1 0.691 | | stk_log_return ALL | .15784 1 0.691 | +------------------------------------------------------------------+结果报告就有了,最后一个是p值。
0
0
0
浏览量2009
攻城狮小明

Stata:DID原理及其操作

本篇可能需要有一点点计量经济学基础。DID(Differences-in-Differences),中文名是双重差分法,看名字就能看出来是两次差分出来的一个结果。在政策研究等领域较为常见,例如一带一路对政策接受国与本国带来的影响。公式形式y = b 0 + b 1 ∗ p a s t + b 2 ∗ ( p a s t ∗ d o ) + b 3 ∗ d o . y = b0+b1*past+b2*(past*do)+b3*do.y=b0+b1∗past+b2∗(past∗do)+b3∗do.假设我的脚标i , t i,ti,t都写了,i ii表示个体,t tt表示时间其中 p a s t pastpast代表是否政策发生节点已经过去,已发生为1,否则为0。例如2013年发一带一路,那么2013年后均为1。d o dodo代表此政策或行为是否被实施,是则为1,否为0。例如一带一路政策接受国为1,其他为0。如果需要加其他控制变量,可自行加入b e t a 4 beta4beta4、b e t a 5 beta5beta5等,开心就好。最终b 2 b2b2(交互项)是否显著能说明能否拒绝政策发挥作用。“双”重差分这两次差分是怎么差的呢,根据前面定义可以得出各组水平如下表(是1就上,0就没有):可以看出来,第一次差分后两组体现出政策发生前后差异,但这并非全部政策造成的影响,政策仅对接受政策或者叫政策实施组有影响,也就是处理组。但是处理组此时发生的变化由政策影响和其他外部因素共同导致,剔除这部分其他因素就靠对照组(非处理组),他们共同接受外部其他因素。对于非处理组变化可以看做是仅由外部因素导致,因此处理组减去此部分变化便为第二次差分。经过两次差分后剩余一项便是交互项系数。当然换一下第一次第二次差分顺序也没问题,换一下表格旋转一下就好了。实际操作就简化为了简单的reg就能解决。
0
0
0
浏览量2008
攻城狮小明

高级计量经济学:Stata实战精要

学习Stata中的格兰杰因果检验、无偏性检验、DID原理、工具变量法(2SLS),解决模型内生性问题。深入理解操作及三种检验代码,提升在时间序列与计量经济学领域的实战能力。
0
0
0
浏览量2075

履历