一、InnoDB事務原理html
1. 事務(Transaction)是數據庫區別於文件系統的重要特性之一,事務會把數據庫從一種一致性狀態轉換爲另外一種一致性狀態。python
2. 在數據庫提交時,能夠確保要麼全部修改都已保存,要麼全部修改都不保存。mysql
二、事務的(ACID)特徵redis
1. 原子性(Atomicity):整個事物的全部操做要麼所有提交成功,要麼所有失敗回滾(不會出現部分執行的狀況)。sql
2. 一致性(Consistency):幾個並行執行的事務,其執行結果必須與按某一順序串行執行的結果相一致。數據庫
3. 隔離性(Isolation):事務的執行不受其餘事務的干擾,事務執行的中間結果對其餘事務必須是透明的。django
4. 持久性(Durability): 一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。網絡
三、事物分類併發
3.1 扁平事務框架
1)扁平事務(Flat Transactions)是事務類型中最簡單但使用最頻繁的事務。
2)在扁平事務中,全部的操做都處於同一層次,由BEGIN/START TRANSACTION開始事務,
由COMMIT/ROLLBACK結束且都是原子的,要麼都執行,要麼都回滾。
3.2 鏈事務
1)鏈事務(Chained Transaction)是指一個事務由多個子事務鏈式組成。
2)前一個子事務的提交操做和下一個子事務的開始操做合併成一個原子操做
3)這樣,在提交子事務時就能夠釋放不須要的數據對象,而沒必要等到整個事務完成後才釋放。
4)鏈事務中的回滾僅限於當前事務,至關於只能恢復到最近的一個保存節點,而帶保存節點的扁平事務能回滾到任意正確的保存點。
3.3 嵌套事務
1)嵌套事務(Nested Transaction)是一個層次結構框架,由一個頂層事務(top-level transaction)控制着各個層次的事務。
2)頂層事務之下嵌套的事務成爲子事務(subtransaction),其控制着每個局部的操做,子事務自己也能夠是嵌套事務。
3)所以,嵌套事務的層次結構能夠當作是一顆樹。
3.4 分佈式事務
1)分佈式事務(Distributed Transactions)一般是一個在分佈式環境下運行的扁平事務,所以須要根據數據所在位置訪問網絡中不一樣節點的數據庫資源。
2)例如一個銀行用戶從招商銀行的帳戶向工商銀行的帳戶轉帳1000元,這裏須要用到分佈式事務,由於不能僅調用某一家銀行的數據庫就完成任務。
四、事物隔離級別
4.1 未提交讀: 髒讀(READ UNCOMMITTED)
1)事務2查詢到的數據是事務1中修改但未提交的數據,但由於事務1回滾了數據
2)因此事務2查詢的數據是不正確的,所以出現了髒讀的問題。
4.2 提交讀: 不可重複讀(READ COMMITTED)
注:一個事務從開始到提交以前對數據所作的改變對其它事務是不可見的,這樣就解決在READ-UNCOMMITTED級別下的髒讀問題。
1)事務2執行update語句但未提交前,事務1的前兩個select操做返回結果是相同的。
2)但事務2執行commit操做後,事務1的第三個select操做就讀取到事務2對數據的改變。
3)致使與前兩次select操做返回不一樣的數據,所以出現了不可重複讀的問題。
4.3 可重複讀: 幻讀(REPEATABLE READ):這是MySQL的默認事務隔離級別
1)事務每開啓一個實例,都會分配一個版本號給它,若是讀取的數據行正在被其它事務執行DELETE或UPDATE操做(即該行上有排他鎖)
2)這時該事物的讀取操做不會等待行上的鎖釋放,而是根據版本號去讀取行的快照數據(記錄在undo log中)
3)這樣,事務中的查詢操做返回的都是同一版本下的數據,解決了不可重複讀問題。
4)雖然該隔離級別下解決了不可重複讀問題,但理論上會致使另外一個問題:幻讀(Phantom Read)。
5)一個事務在執行過程當中,另外一個事物對已有數據行的更改,MVCC機制可保障該事物讀取到的原有數據行的內容相同
6)但並不能阻止另外一個事務插入新的數據行,這就會致使該事物中憑空多出數據行,像出現了幻讀同樣,這即是幻讀問題。
4.4 可串行讀(SERIALIZABLE)
1)這是事務的最高隔離級別,經過強制事務排序,使之不可能相互衝突,就是在每一個讀的數據行加上共享鎖來實現。
2)在該隔離級別下,能夠解決前面出現的髒讀、不可重複讀和幻讀問題,但也會致使大量的超時和鎖競爭現象,通常不推薦使用。
一、MyISAM和InnoDB支持的鎖類型
1. 相對其餘數據庫而言,MySQL的鎖機制比較簡單,其最顯著的特色是不一樣的存儲引擎支持不一樣的鎖機制。
2. MyISAM和MEMORY存儲引擎採用的是表級鎖(table-level locking)。
3. InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認狀況下是採用行級鎖。
二、MySQL這3種鎖的特性
1)行級鎖
1. 行級鎖分爲共享鎖和排它鎖,行級鎖是Mysql中鎖定粒度最細的鎖。
2. InnoDB引擎支持行級鎖和表級鎖,只有在經過索引條件檢索數據的時候,才使用行級鎖,否就使用表級鎖。
3. 行級鎖開銷大,加鎖慢,鎖定粒度最小,發生鎖衝突機率最低,併發度最高
舉例: 只根據主鍵進行查詢,而且查詢到數據,主鍵字段產生行鎖。
#### 行鎖 ''' client1中執行: select * from shop where id=1 for update; clenet2中執行: select * from shop where id=2 for update; # 能夠正常放回數據 select * from shop where id=1 for update; # 阻塞 ''' # 能夠看到:id是主鍵,當在client1上查詢id=1的數據時候,在client2上查詢id=2的數據沒問題 # 但在client2上查詢id=1的數據時阻塞,說明此時的鎖時行鎖。 # 當client1執行commit時,clinet2查詢的id=1的命令當即返回數據。
2)表級鎖
1. 表級鎖分爲表共享鎖和表獨佔鎖。
2. 表級鎖開銷小,加鎖快,鎖定粒度大、發生鎖衝突最高,併發度最低
舉例:根據非主鍵不含索引(name)進行查詢,而且查詢到數據,name字段產生表鎖。
#### 表鎖 # 能夠看到,client1經過非索引的name字段查詢到prod11的數據後,在client2查prod**的數據會阻塞,產生表鎖。 ''' client1中執行: select * from shop where name="prod11" for update; clenet2中執行: select * from shop where name="prod**" for update; '''
3)頁級鎖
1. 頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。
2. 表級鎖速度快,但衝突多,行級衝突少,但速度慢。
3. 因此取了折衷的頁級,一次鎖定相鄰的一組記錄,BDB支持頁級鎖。
4. 開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。
總結:
1. 表級鎖更適合於以查詢爲主,只有少許按索引條件更新數據的應用,如Web應用;
2. 而行級鎖則更適合於有大量按索引條件併發更新少許不一樣數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統。
三、鎖分類
1. 按操做劃分:DML鎖,DDL鎖
2. 按鎖的粒度劃分:表級鎖、行級鎖、頁級鎖
3. 按鎖級別劃分:共享鎖、排他鎖
4. 按加鎖方式劃分:自動鎖、顯示鎖
5. 按使用方式劃分:樂觀鎖、悲觀鎖
四、樂觀鎖悲觀鎖做用
1. 在併發訪問狀況下,頗有可能出現不可重複讀等等讀現象。
2. 爲了更好的應對高併發,封鎖、時間戳、樂觀併發控制(樂觀鎖)、
悲觀併發控制(悲觀鎖)都是併發控制採用的主要技術方式。
五、悲觀鎖
1. 悲觀鎖的實現,每每依靠數據庫提供的鎖機制
2. MySQL會對查詢結果集中每行數據都添加排他鎖,其餘線程對該記錄的更新與刪除操做都會阻塞,排他鎖包含行鎖、表鎖。
3. 申請前提:沒有線程對該結果集中的任何行數據使用排他鎖或共享鎖,不然申請會阻塞。
適用場景:悲觀鎖適合寫入頻繁的場景。
注:
首先咱們須要set autocommit=0,即不容許自動提交
用法:select * from tablename where id = 1 for update;
六、樂觀鎖
1. 在更新數據的時候須要比較程序中的庫存量與數據庫中的庫存量是否相等,若是相等則進行更新。
2. 反之程序從新獲取庫存量,再次進行比較,直到兩個庫存量的數值相等才進行數據更新。
七、舉例:對商品數量-1操做
1)悲觀鎖實現方法
1. 每次獲取商品時,對該商品加排他鎖。
2. 也就是在用戶A獲取獲取 id=1 的商品信息時對該行記錄加鎖,期間其餘用戶阻塞等待訪問該記錄。
#### 悲觀鎖實現加一操做代碼 # 咱們能夠看到,首先經過begin開啓一個事物,在得到shop信息和修改數據的整個過程當中都對數據加鎖,保證了數據的一致性。 ''' begin; select id,name,stock as old_stock from shop where id=1 for update; update shop set stock=stock-1 where id=1 and stock=old_stock; commit '''
2)樂觀鎖實現方法
1. 每次獲取商品時,不對該商品加鎖。
2. 在更新數據的時候須要比較程序中的庫存量與數據庫中的庫存量是否相等,若是相等則進行更新
3. 反之程序從新獲取庫存量,再次進行比較,直到兩個庫存量的數值相等才進行數據更新。
#### 樂觀鎖實現加一操做代碼 # 咱們能夠看到,只有當對數量-1操做時纔會加鎖,只有當程序中值和數據庫中的值相等時才正真執行。 ''' //不加鎖 select id,name,stock where id=1; //業務處理 begin; update shop set stock=stock-1 where id=1 and stock=stock; commit; '''
八、python適用樂觀鎖解決事物問題
使用 django.db.transaction 模塊解決MySQL 事物管理 問題
1. 在事務當前啓動celery異步任務, 沒法獲取未提交的改動.
2. 在使用transaction當中, Model.save()都不作commit .
3. 所以若是在transaction當中設置異步任務,使用get()查詢數據庫,將看不到對象在事務當中的改變.
4. 這也是實現」可重複讀」的事務隔離級別,即同一個事務裏面的屢次查詢都應該保持結果不變.
# with語句用法 from django.db import transaction def viewfunc(request): # 這部分代碼不在事務中,會被Django自動提交 ... with transaction.atomic(): # 這部分代碼會在事務中執行 ... ''' from django.db import transaction # 建立保存點 save_id = transaction.savepoint() # 回滾到保存點 transaction.savepoint_rollback(save_id) # 提交從保存點到當前狀態的全部數據庫事務操做 transaction.savepoint_commit(save_id) '''
from django.db import transaction def create(self, validated_data): """ 保存訂單 """ # 獲取當前下單用戶 user = self.context['request'].user # 組織訂單編號 20170903153611+user.id # timezone.now() -> datetime order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id) address = validated_data['address'] pay_method = validated_data['pay_method'] # 生成訂單 with transaction.atomic(): # 建立一個保存點 save_id = transaction.savepoint() try: # 建立訂單信息 order = OrderInfo.objects.create( order_id=order_id, user=user, address=address, total_count=0, total_amount=Decimal(0), freight=Decimal(10), pay_method=pay_method, status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else OrderInfo.ORDER_STATUS_ENUM['UNPAID'] ) # 獲取購物車信息 redis_conn = get_redis_connection("cart") redis_cart = redis_conn.hgetall("cart_%s" % user.id) cart_selected = redis_conn.smembers('cart_selected_%s' % user.id) # 將bytes類型轉換爲int類型 cart = {} for sku_id in cart_selected: cart[int(sku_id)] = int(redis_cart[sku_id]) # 一次查詢出全部商品數據 skus = SKU.objects.filter(id__in=cart.keys()) # 處理訂單商品 for sku in skus: sku_count = cart[sku.id] # 判斷庫存 origin_stock = sku.stock # 原始庫存 origin_sales = sku.sales # 原始銷量 if sku_count > origin_stock: transaction.savepoint_rollback(save_id) raise serializers.ValidationError('商品庫存不足') # 用於演示併發下單 # import time # time.sleep(5) # 減小庫存 new_stock = origin_stock - sku_count new_sales = origin_sales + sku_count sku.stock = new_stock sku.sales = new_sales sku.save() # 累計商品的SPU 銷量信息 sku.goods.sales += sku_count sku.goods.save() # 累計訂單基本信息的數據 order.total_count += sku_count # 累計總金額 order.total_amount += (sku.price * sku_count) # 累計總額 # 保存訂單商品 OrderGoods.objects.create( order=order, sku=sku, count=sku_count, price=sku.price, ) # 更新訂單的金額數量信息 order.total_amount += order.freight order.save() except ValidationError: raise except Exception as e: logger.error(e) transaction.savepoint_rollback(save_id) raise # 提交事務 transaction.savepoint_commit(save_id) # 更新redis中保存的購物車數據 pl = redis_conn.pipeline() pl.hdel('cart_%s' % user.id, *cart_selected) pl.srem('cart_selected_%s' % user.id, *cart_selected) pl.execute() return order
九、MySQL中 共享鎖 和 排它鎖
1)排它鎖
1. 排它鎖又叫寫鎖,若是事務T對A加上排它鎖,則其它事務都不能對A加任何類型的鎖。獲准排它鎖的事務既能讀數據,又能寫數據。
2. 用法 : SELECT … FOR UPDATE
2)共享鎖(share lock)
1. 共享鎖又叫讀鎖,若是事務T對A加上共享鎖,則其它事務只能對A再加共享鎖,不能加其它鎖。
2. 獲准共享鎖的事務只能讀數據,不能寫數據。
3. 用法: SELECT … LOCK IN SHARE MODE;