事务 & 锁

事务的概念

事务的概念来自于两个独立的需求:并发数据库访问,系统错误恢复。
一个事务是可以被看作一个单元的一系列SQL语句的集合。

事务的特性(ACID)

  • A,atomcity 原子性
    事务必须是原子工作单位;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的曹祖具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。
  • C,consistency 一致性
    事务将数据库从一种一致状态转变为下一种一致状态。也就是说,事务在完成时,必须使所有的数据都保持一致状态(各种 constraint 不被破坏)。
  • I,isolation 隔离性
    由并发事务所作的修改必须与任何其他并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。换句话说,一个事务的影响在该事务提交前对其他事务都不可见。
  • D,durability 持久性
    事务完成之后,它对于系统的影响是永久性的。该修改即使出现指明的系统古战也将一致保持。

事务的隔离级别

如果不对数据库进行并发控制,可能会产生异常情况:

  1. 脏读(Dirty Read)
    当一个事务读取另一个事务尚未提交的修改时,产生脏读。
    同一事务内不是脏读。一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚,也就是说读取除的数据其实是错误的。
  2. 非重复读(Nonrepeatable Read)
    一个事务对同一行数据重复读取两次,但是却得到了不同的结果。同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。
  3. 幻象读(Phantom Read,幻读)
    事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
    当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会产生幻读的问题。
  4. 丢失修改(Lost Update)
    第一类:当两个事务更新相同的数据源,如果第一个事务被提交,第二个被撤销,那么连同第一个事务做的更新也被撤销。
    第二类:有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失败。

为了兼顾并发效率和异常控制,在标准SQL规范中,定义了4个事务隔离级别,(Oracle 和 SQL Server 对标准隔离级别有不同的实现)

  1. 未提交读(Read Uncommitted)
    直译就是“读为提交”,意思是即使一个更新语句没有提交,但是别的事务可以读到这个改变。允许脏读。
  2. 已提交读(Read Committed)
    直译就是“读提交”,意思是语句提交以后,即执行了 Commit 以后别的事务就能读到这个改变,只能读取到已经提交的数据。 Oracle等多数数据库默认都是该级别。
    不允许脏读,但会出现非重复读。
  3. 可重复度(Repeatable Read)
    是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的。
    不允许脏读,不允许非重复度,但是会出现幻读。
  4. 串行读(Serializable)
    序列化,是说这个事务执行的时候不允许别的事务并发执行。完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
    不允许不一致现象的出现。

事务隔离的实现——锁

  1. 共享锁(S锁)
    用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。
  2. 更新锁(U锁)
    用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
  3. 独占所(X锁,也叫排他锁)
    一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享锁。写是独占锁,可以有效的防止“脏读”。

Read Uncommitted 如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

Read Committed 读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。可以通过“瞬间共享读锁”和“排他写锁”实现。

Repeatable Read 读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。可以通过“共享读锁”和“排他写锁”实现。

Serializable 读加共享锁,写加排他锁,读写互斥。

乐观锁 悲观锁

基本概念

乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。

  • 乐观锁:在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
  • 悲观锁:在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

实现方式

明确:乐观锁和悲观锁是两种思想,它们的使用是非常广泛的,不局限于某种编程语言或数据库。

悲观锁的实现方式是加锁,加锁既可以是对代码块加锁,也可以是对数据加锁(如MySQL中的排他锁)。

乐观锁的实现方式主要有两种:CAS机制和版本号机制。

CAS(Compare And Swap)

CAS操作包括了3个操作:

  • 需要读写的内存位置(V)
  • 进行比较的预期值(A)
  • 拟写入的新值(B)

CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。
这里引出一个新问题,既然CAS包含了Compare和Swap两个操作,它又如何保证原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。

版本号机制

除了CAS,版本号机制也可以用来实现乐观锁。版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1.当某个线程查询数据时,将该数据的版本号一起查出来;当该新城更新数据时,判断当前版本号与之前的版本号是否一直,如果一直才进行操作。
需要主义的是,这里使用了版本号作为判断数据变化的标记,实际上可以根据实际情况选用其他能够编辑数据版本的字段,如时间戳等。

优缺点和适用场景

乐观锁和悲观锁并没有优劣之分,他们有格子适合的场景。

  1. 功能限制
    与悲观锁相比,乐观锁适用的场景受到了更多的限制,无路是CAS还是版本号机制。
    例如,CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的,而synchronized则可以通过对整个代码块加锁来处理。再比如版本号机制,如果query的时候是针对表1,而update的时候是针对表2,也很难通过简单的版本号来实现乐观锁。
  2. 竞争激烈程度
    如果悲观锁和乐观锁都可以使用,那么选择就要考虑竞争的激烈程度:
  • 当竞争不激烈(出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。
  • 当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。

面试官追问:乐观锁加锁吗?

  1. 乐观锁本身是不加锁的,只是在更新时判断一下数据是否被其他线程更新了。
  2. 有时乐观锁可能与加锁操作合作,例如,MySQL在执行update时会加排他锁,但这只是乐观锁与加锁操作合作的例子,不能改变“乐观锁本身不加锁”这一事实。

面试官追问:CAS有哪些缺点?

CAS这种实现方式有什么缺点?

  1. ABA问题:
    假设有两个线程——线程1和线程2,两个线程按照顺序进行一下操作:
    (1) 线程1读取内存中数据为A
    (2) 线程2将该数据修改为B
    (3) 线程2将该数据修改为A
    (4) 线程1对数据进行CAS操作
    在第(4)步中,由于内存中数据仍然为A,因此CAS操作成功,但实际上该数据已经被线程2修改过了。这就是ABA问题。
    ABA似乎没有什么危害。但在某些场景下,ABA却会带来隐患,例如栈顶问题:一个栈的栈顶经过两次(或多次)变化又恢复了原值,但是栈可能已发生了变化。
    对于ABA问题,比较有效的方案是引入版本号,内存中的值发生一次变化,版本号都+1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。
  2. 高竞争下的开销问题
    在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。针对这个问题的一个思路是引入退出机制,如重试次数超过一定阀值后失败退出。当然,更重要的是避免在高竞争环境下使用乐观锁。
  3. 功能限制
    CAS的功能比较受限,例如CAS只能保证单个变量(或者说单个内存值)操作的原子性,这意味着:
    (1) 原子性不一定能保证线程安全
    (2) 当涉及到多个变量(内存值)时,CAS也无能为力
    除此之外,CAS的实现需要硬件层面处理器的支持,灵活性受到限制。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,709评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 6,876评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,583评论 2 9