# lock https://chanjarster.github.io/post/mysql/gap-lock-next-key-lock/ https://segmentfault.com/a/1190000040129107 https://www.itread01.com/content/1544864424.html 大概說用啥redis lock https://blog.csdn.net/qiling_70/article/details/116272249 必看 https://www.bilibili.com/video/BV11t411L7tM?p=3&spm_id_from=pageDriver 必要看 基礎 https://www.bilibili.com/video/BV1Kr4y1i7ru?p=130&vd_source=f726dd30598fe01d9fbc9c5a988d6408 https://juejin.cn/post/7018137095315128328 排他 vs 自選 vs 樂觀 https://cloud.tencent.com/developer/article/2217673  lock說明 https://www.bilibili.com/video/BV1Vh4y1f7RX/?spm_id_from=333.337.search-card.all.click&vd_source=f726dd30598fe01d9fbc9c5a988d6408ㄏ ``` 鎖分類 從資料庫系統角度分為三種:排他鎖、共享鎖、更新鎖。 從程式設計師角度分為兩種:一種是悲觀鎖,一種樂觀鎖。。 ``` 在存在并发操作的时候,必然需要一种机制来保证数据的完整性与一致性。锁就是这一技术的实现。 Record Lock表示記錄鎖,鎖的是索引記錄。 Gap Lock是間隙鎖,鎖的是索引記錄之間的間隙。 Next-Key Lock是Record Lock和Gap Lock的組合,同時鎖索引記錄和間隙。他的範圍是左開右閉的。  如果查詢的那行 沒有index 會搜尋整張表 因為mysql要先保證資料的穩定 ## db ACID https://www.cnblogs.com/crazymakercircle/p/13917517.html ## 科夫曼条件  ## 活锁 ## 樂觀vs悲觀情境 https://juejin.cn/post/7396921416464105487 ## lock規則 加锁规则 加锁基本单位next-key lock,next-key lock = 间隙锁 + 行锁,前开后闭 查询过程中访问到的对象都要加锁 索引等值查询,给唯一索引加锁时,next-key lock会退化为行锁 索引等值查询,向右遍历时且最后一个值不满足查询条件,next-key lock会退化为间隙锁 索引上的范围查询会访问到不满足条件的第一个值为止 ## 悲觀鎖的 exclusive lock 在mysql  這樣where那個會等別的阻塞完成才會查詢  select-for-update再insert造成deadlock的陷阱/ https://notes.andywu.tw/2021/select-for-update%E5%86%8Dinsert%E9%80%A0%E6%88%90deadlock%E7%9A%84%E9%99%B7%E9%98%B1/ https://stackoverflow.com/questions/3601895/does-select-for-update-prevent-other-connections-inserting-when-the-row-is-not for update(事務鎖) + 外key 死鎖 https://yuanchieh.page/posts/2020/2020-12-26_mysql-deadlock-%E5%95%8F%E9%A1%8C%E6%8E%92%E6%9F%A5%E8%88%87%E8%99%95%E7%90%86/ ## sharedLock  ## 樂觀鎖 利用版本號 這樣當她where的時候會找不到 ## 範圍鎖   這樣如果插入 新的沒問題 只要不再範圍內的都可以新增或更新 但如果你是 id>=1這樣 你一定會錯 因為新增的一定> 1 這就是範圍鎖 ## 鎖定資料 Lock https://kejyuntw.gitbooks.io/mysql-learning-notes/content/transaction/transaction-lock.html 鎖定資料有 shared lock (sharedLock) 與 lock for update (lockForUpdate),兩者都可以避面同一行資料被其他的 transaction update ## MySQL中锁的种类 MySQL中锁的种类很多,有常见的表锁和行锁,也有新加入的Metadata Lock等等,表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般是做ddl处理时使用。 行锁则是锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强,MySQL一般都是用行锁来处理并发事务。这里主要讨论的也就是行锁 锁种类 根据概念分:悲观锁和乐观锁 根据粒度分:表锁、页锁、行锁,最常见的就是表锁和行锁。其中,MyISAM引擎只有表锁,而InooDB既有表锁也有行锁。 根据功能分:共享锁、排它锁(独占锁)、意向锁等。其中,共享锁被称为 S 锁。排它锁称为 X 锁。 锁名称 特点 表锁 加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。 行锁 开销大,发生锁冲突概率低。并发度高,会发生死锁。 页锁 开销、加锁时间、锁定粒度界于表锁和行锁之间,会出现死锁,并发度一般 ## 一次封锁or两段锁? 因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。 数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁) 加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。  这种方式虽然无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。 ## InnoDB 行锁 資料庫能夠確定那些行需要鎖的情況下使用行鎖,如果不知道會影響哪些行的時候就會使用表鎖。 舉個例子,一個使用者表user,有主鍵id和使用者生日birthday。 當你使用update … where id=?這樣的語句時,資料庫明確知道會影響哪一行,它就會使用行鎖; 當你使用update … where birthday=?這樣的的語句時,因為事先不知道會影響哪些行就可能會使用表鎖。 所以索引很重要 ### 共享锁(S 锁) 概念:又名读锁,对某一资源加共享锁,自身可以修改或读取该资源,其它人也能继续持有该资源的共享锁,无法持有该资源的排它锁。并只能读取,不能进行其它操作。 一个会话给一个表中的某一行加共享锁,其它会话可读不可进行其它操作,直到锁释放 一个会话给一个表中的某一行加共享锁,不影响该会话操作其它表,以及自身的表,这与表锁不同(表锁是当前会话给该表加表锁后,那当前会话只能操作该表中的数据,不能在进行操作其它表中的数据了) 当一个会话持有某行的共享锁,其它会话也可在持有某行的共享锁,但是两者同时修改这条数据的话会造成死锁 ### InnoDB 排它锁(X 锁) 概念:又名写锁,对某一资源加排它锁,自身可以修改或读取该资源,其它会话不能继续持有该资源的共享锁和排它锁。并只能对加锁数据进行读取,不能进行其它操作。 ``` 排他锁的申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞 for update 及 lock in share mode 仅适用于 InnoDB,且必须在事务块 (BEGIN/COMMIT) 中才能生效,在进行事务操作时,通过 for update 语句,MySQL 会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞,排他锁包含 行锁、表锁 ```  ### 更新鎖 U鎖,在修改操作的初始化階段用來鎖定可能要被修改的資源,這樣可以避免使用共享鎖造成的死鎖現象。 因為當使用共享鎖時,修改資料的操作分為兩步: 首先獲得一個共享鎖,讀取資料, 然後將共享鎖升級為排他鎖,再執行修改操作。 這樣如果有兩個或多個事務同時對一個事務申請了共享鎖,在修改資料時,這些事務都要將共享鎖升級為排他鎖。這時,這些事務都不會釋放共享鎖,而是一直等待對方釋放,這樣就造成了死鎖。 如果一個數據在修改前直接申請更新鎖,在資料修改時再升級為排他鎖,就可以避免死鎖。 性質 1. 用來預定要對此頁施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖; 1. 當被讀取的頁要被更新時,則升級為X鎖; 1. U鎖一直到事務結束時才能被釋放 ## 四、锁的算法(行锁) ### 1、记录锁: 窗口 1:直接锁住 id 为 1 的记录 ``` mysql> select * from student; +----+--------+ | id | name | +----+--------+ | 1 | 小王 | | 2 | 李 | | 3 | 张 | +----+--------+ 3 rows in set (0.00 sec) ``` ``` mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from student where id = 1 for update; +----+--------+ | id | name | +----+--------+ | 1 | 小王 | +----+--------+ 1 row in set (0.00 sec) ``` 窗口 2: 则不能对 id 为 1 的记录进行修加锁以及增删改操作,但是可以查出 ``` mysql> select * from student where id = 1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction ``` ### 2、间隙锁: 间隙锁(Gap Lock)是 Innodb 在可重复读提交下为了解决幻读问题时引入的锁机制。 ### 3、临键锁 临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,解决幻读问题。 ## 悲观锁与乐观锁的详解 悲观锁与乐观锁是人们定义出来的概念,你可以理解为一种思想,是处理并发资源的常用手段。 不要把他们与 mysql 中提供的锁机制 (表锁,行锁,排他锁,共享锁) 混为一谈。 ### 一、悲观锁 顾名思义,就是对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。 悲观锁的实现,通常依靠数据库提供的锁机制实现,比如 mysql 的排他锁,select …. for update 来实现悲观锁。 事务提交时会释放事务过程中的锁。 悲观锁在并发控制上采取的是先上锁然后再处理数据的保守策略,虽然保证了数据处理的安全性,但也降低了效率。 ### 二、乐观锁  顾名思义,就是对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。 如果发现冲突了,则返回错误信息给用户,让用户自已决定如何操作。 乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号,一种是通过时间戳。 给表加一个版本号或时间戳的字段,读取数据时,将版本号一同读出,数据更新时,将版本号加 1。 当我们提交数据更新时,判断当前的版本号与第一次读取出来的版本号是否相等。如果相等,则予以更新,否则认为数据过期,拒绝更新,让用户重新操作。 樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。 **樂觀鎖的實現方式** #### 版本號(version) 版本號(記為version):就是給資料增加一個版本標識,在資料庫上就是表中增加一個version欄位,每次更新把這個欄位加1,讀取資料的時候把version讀出來,更新的時候比較version,如果還是開始讀取的version就可以更新了,如果現在的version比老的version大,說明有其他事務更新了該資料,並增加了版本號,這時候得到一個無法更新的通知,使用者自行根據這個通知來決定怎麼處理,比如重新開始一遍。這裡的關鍵是判斷version和更新兩個動作需要作為一個原子單元執行,否則在你判斷可以更新以後正式更新之前有別的事務修改了version,這個時候你再去更新就可能會覆蓋前一個事務做的更新,造成第二類丟失更新,所以你可以使用update … where … and version=”old version”這樣的語句,根據返回結果是0還是非0來得到通知,如果是0說明更新沒有成功,因為version被改了,如果返回非0說明更新成功。 #### 時間戳(使用資料庫伺服器的時間戳) 時間戳(timestamp):和版本號基本一樣,只是通過時間戳來判斷而已,注意時間戳要使用資料庫伺服器的時間戳不能是業務系統的時間。 #### 欄位 使用欄位做版本控制資訊,只有欄位在修改過程中沒變化才會執行更新。 **樂觀鎖幾種方式的區別** 新系統設計可以使用version方式和timestamp方式,需要增加欄位,應用範圍是整條資料,不論那個欄位修改都會更新version,也就是說兩個事務更新同一條記錄的兩個不相關欄位也是互斥的,不能同步進行。舊系統不能修改資料庫表結構的時候使用資料欄位作為版本控制資訊,不需要新增欄位,待更新欄位方式只要其他事務修改的欄位和當前事務修改的欄位沒有重疊就可以同步進行,併發性更高。 併發控制會造成活鎖和死鎖,就像作業系統那樣,會因為互相等待而導致。 **活鎖** 定義:指的是T1封鎖了資料R,T2同時也請求封鎖資料R,T3也請求封鎖資料R,當T1釋放了鎖之後,T3會鎖住R,T4也請求封鎖R,則T2就會一直等待下去。 解決方法:採用“先來先服務”策略可以避免。 **死鎖** 定義:就是我等你,你又等我,雙方就會一直等待下去。比如:T1封鎖了資料R1,正請求對R2封鎖,而T2封住了R2,正請求封鎖R1,這樣就會導致死鎖,死鎖這種沒有完全解決的方法,只能儘量預防 **預防方法:** ###### 一次封鎖法,指的是一次性把所需要的資料全部封鎖住,但是這樣會擴大了封鎖的範圍,降低系統的併發度; 順序封鎖法,指的是事先對資料物件指定一個封鎖順序,要對資料進行封鎖,只能按照規定的順序來封鎖,但是這個一般不大可能的。 系統判定死鎖的方法: ###### 超時法:如果某個事物的等待時間超過指定時限,則判定為出現死鎖; 等待圖法:如果事務等待圖中出現了迴路,則判斷出現了死鎖。 對於解決死鎖的方法,只能是撤銷一個處理死鎖代價最小的事務,釋放此事務持有的所有鎖,同時對撤銷的事務所執行的資料修改操作必須加以恢復。 ## 各自適用場景 悲觀鎖和樂觀鎖是資料庫用來保證資料併發安全,防止更新丟失的兩種方法,兩者大部分場景下差異不大,一些獨特場景下有一些差別,一般我們可以從以下幾方面來分析: 1. 響應速度 :如果需要非常高的響應速度,建議採用 樂觀鎖 方案,成功就執行,不成功就失敗,不需要等待其他併發去釋放鎖; 1. 衝突頻率 :如果衝突頻率非常高,建議採用悲觀鎖,保證成功率,如果衝突頻率大,樂觀鎖會需要重試多次才能成功,代價比較大; 1. 重試代價 :如果重試代價大,建議採用悲觀鎖; ###### tags: `MySql`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up