MySQL優化(三):優化數據庫對象


二、優化數據庫對象

1、優化表的數據類型

      應用設計的時候需要考慮字段的長度留有一定的冗余,但不推薦很多字段都留有大量的冗余,這樣既浪費磁盤空間,也在應用操作時浪費物理內存。

      在MySQL中,可以使用函數PROCEDURE ANALYSE()對表進行分析,給出優化建議。(16,  256)是指不為包含的值多於16或者256字節的ENUM類型提出建議。

      

      Optimal_fieldtype為優化建議,可以通過alter修改字段類型:ALTER TABLE TAB_NAME MODIFY COLUMN Optimal_fieldtype_VALUE;

2、逆規范化

      反規划的好處是降低連接操作的需求、降低外碼和索引的數目、減少表的數目,帶來的問題可能會出現數據的完整性問題,雖然查詢加快,但是會降低修改速度。在進行反規范操作之前,要充分考慮數據的存取需求、常用表的大小、一些特殊的計算、數據的物理存儲位置。常用的反規范技術如下:

      - 增加冗余列:指在多個表中具有相同的列,它常用來查詢時避免連接操作。

      - 增加派生列:指增加的列來自其他表中的數據,由其他表中的數據經過計算生成,作用是減少連接操作,避免使用集函數。

      - 重新組表:指如果許多用戶需要查看兩個表連接出來的結果數據,則把這兩個表重新組成一個表來減少連接而提高性能。

      - 分庫分表:https://www.cnblogs.com/dukuan/p/9480610.html

3、使用中間表提高統計查詢速度

      對於數據量較大的表,在其上進行統計查詢通常會效率很低,並且還會對線上應用產生負面影響。此種情況下可以使用中間表提高統計查詢的效率。

      一般步驟:創建表結構和原表結構相同的表,遷移數據需要統計的數據,進行統計。

      中間表在統計查詢中的優點:

          - 中間表復制源表數據,並且與源表相“隔離”,在中間表上做統計查詢不會對線上應用產生負面影響。

          - 中間表上可以靈活的添加索引或增加臨時用的新字段,從而達到提高統計查詢效率和輔助統計查詢作用。 

三、鎖問題

1、鎖概述

      MyISAM和MEMORY存儲引擎采用表級鎖,BDB存儲引擎(MySQL5.1后不直接支持此存儲引擎)采用的頁面鎖,也支持表級鎖,InnoDB支持行級鎖和表級鎖,默認是行級。

      - 表級鎖:開銷小,加鎖快,不會出現死鎖,鎖定粒度大,發生鎖沖突的概率最高,並發度最低。

      - 行級鎖:開銷大,加鎖慢,會出現死鎖,鎖定粒度小,發生沖突的概率最低,並發度也最高。

      - 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間,會出現死鎖,鎖定粒度界於表鎖和行鎖之間,並發度一般。

      表級鎖更適合以查詢為主,只有少量按索引條件更新數據的應用,如Web應用。而行級鎖更適合有大量按索引條件並發更新少量不同數據,同時又有並發查詢的應用,如在線事物處理系統。

2、MyISAM表鎖

      MyISAM只支持表鎖。

2.1 查詢表級鎖爭用情況

      

      Table_locks_immediate:產生表級鎖定的次數。

      如果Table_locks_waited比較高,說明存在着嚴重的表級鎖爭用情況。

2.2 MySQL表級鎖的鎖模式

      表級鎖分為表共享讀鎖和表獨占寫鎖。兼容性如下:

      

      對於MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一個表的寫請求。對表的寫操作,會阻塞同一表的讀和寫。MyISAM讀操作和寫操作以及寫操作之間是串行的。

2.3 如何加鎖表

      MyISAM在執行SELECT、UPDATE、DELETE、INSERT前,會自動給涉及的表加鎖,無需用戶干預。

      顯式加鎖情況:比如同時要查詢或者比對兩個表中的內容,為防止在查其中一個時另一個有更新或者新數據,此時需要顯式的為兩個表加鎖。

      加鎖命令:LOCK TABLES tab_name READ LOCAL, tab_name2 READ LOCAL;  -- LOCAL表示允許其他用戶在MyISAM表尾並發插入記錄。使用顯式加鎖時,必須同時取得所有涉及表的鎖,而且加鎖后只能訪問加鎖的這些表,不能訪問其他表。並且如果加的是讀鎖,那么只能執行查詢操作。並且加鎖時需要對別名也要加鎖。

2.4 MyISAM並發插入

      MyISAM通過concurrent_insert參數決定是否允許並發插入

      - 0:不允許並發插入

      - 1:MyISAM表中無空洞,允許讀的同時在表末尾插入記錄。默認設置

      - 2:無論是否有無空洞,都能插入。

      可以利用並發插入特性來解決應用中對同一表查詢和插入的鎖爭用。同時定期在系統空閑時整理空間碎片,收回因刪除記錄而產生的中間空洞。

      注意:只能insert不能update和delete。且鎖表的session不能獲取到新插入到的數據。

2.5 MyISAM的鎖調度

      同一時刻請求的寫鎖和讀鎖,MySQL會優先處理寫進程。即使是讀請求先到等待隊列,寫鎖也會插入到讀鎖請求之前,這也是MyISAM表不太適合於有大量更新操作和查詢操作應用的原因。

      調節MyISAM的調度行為:

      - 通過制定啟動參數low-priority-updates, 使MyISAM引擎默認給予讀請求以優先的權利。

      - 通過執行命令 SET LOW_PRIORITY_UPDATES = 1,來降低更新請求的優先級。

      - 通過制定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先級。

      MySQL也提供了一種折中的辦法,即給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個值后,MySQL就暫時將寫請求的優先級降低。

3、InnoDB鎖問題

3.1 背景知識

3.1.1 事務及其ACID屬性

      事務是由一組SQL語句組成的邏輯處理單元,具有以下4個屬性,通常簡稱為事務的ACID屬性。

      - 原子性:事務是一個原子操作單元,其對數據的修改,要么全部執行,要么全都不執行。

      - 一致性:在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構也都必須是正確的。

      - 隔離性:數據庫系統提供一定的隔離機制,保證事務在不受外部並發操作影響的獨立環境執行。

      - 持久性:事務完成之后,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。

3.1.2 並發事務處理的問題

      相對於串行處理來說,並發事務處理能大大增加數據庫資源利用率,提供吞吐量,但會引起下列問題:

      - 更新丟失:兩個或多個事務同時操作同一行,會覆蓋其他事務的更新。

      - 臟讀:當一個事務在對一條記錄做修改未完成並提交前,另一個事務來讀取同一條記錄,這時讀取到的數據叫臟讀。

      - 不可重復讀:一個事務在讀取某些數據后的某個時間,再次讀取,發現讀出的數據已經發生了改變或某些記錄已經被刪除了。

      - 幻讀:一個事務按相同的查詢條件重新讀取以前檢索過的數據,其他事務插入了滿足其條件的新數據。

3.1.3 事務隔離級別

      線上業務應完全避免更新丟失,但是避免此情況需要應用程序對要更新的數據加必要的鎖來解決。但是關於讀一致性,必須由數據庫提供一定的事務隔離機制來解決。數據庫事務隔離的方式,基本上可分為以下兩種:

      - 在讀取數據之前,對其加鎖。 

      - 使用數據多版本並發控制(MVCC/MCC),按照請求時間點創建快照。

      數據庫的事務隔離越嚴格,並發副作用越小,相應的代價也就越大,因為事務隔離實質是進行"串行化"。

      為了解決隔離和並發的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別。應用可以根據自己的業務邏輯要求,選擇不同的隔離級別來平衡隔離和並發的矛盾。

      

3.2 獲取InnoDB行鎖爭用情況

      

      如果Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比較高,則爭用比較嚴重。

      此時可以通過查詢information_schema數據庫中的表來查看鎖情況,或者通過設置InnoDB Monitors來觀察發生鎖沖突的表、數據行等。

      - 通過information_schema

        SELECT * FROM innodb_locks;

        SELECT * FROM innodb_lock_waits;

      - 通過InnoDB Monitors

        CREATE TABLE innodb_monitor(a INT) ENGINE = INNODB;

        然后通過:SHOW ENGINE INNODB STATUS查看

        關閉監視器:DROP TABLE innodb_monitor;

3.3 InnoDB行鎖模式及加鎖方法

      InnoDB實現了兩種類型的行鎖:

      - 共享鎖:允許另一個事務也獲得共享鎖,但是阻止其他事務獲得相同數據集的排他鎖。

      - 排他鎖:允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。

      事務獲取鎖的方式:

      - 共享鎖:SELECT ... WHERE ... LOCK IN SHARE MODE;

      - 排他鎖:SELECT ... WHERE ... FOR UPDATE;

      對於鎖定行后需要進行更新操作的應用,用過使用排他鎖。

3.4 InnoDB行鎖實現方式

      InnoDB行鎖是通過索引上的索引項加鎖來實現的,如果沒有索引,InnoDB將通過隱藏的聚簇索引來記錄加鎖。分為3種情形:

      - Record lock:對索引項加鎖。

      - Gap lock:對索引項之間的“間隙”、第一條記錄前的“間隙”或最后一條記錄后的“間隙”加鎖。

      - Next-key lock:前兩種的組合,對記錄及其前面的間隙加鎖。

      注意:如果不通過索引條件檢索數據,那么InnoDB將對表中的所有記錄加鎖,等同於表鎖,生產環境中需要注意這一特性防止導致大量的鎖沖突,從而影響並發性能。

      由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果使用相同的索引建,會出現鎖沖突。

      當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,但是當不同的session查詢相同的數據時,同樣會阻塞。

      當表的數據較少,此時MySQL可能會全盤掃描,此時會導致不使用索引查詢,進而導致全表加鎖。

      MySQL檢索的數據類型與索引字段不同,會進行數據轉換,但卻不會使用索引,所以會導致InnoDB對所有的記錄加鎖。

3.5 Next-Key鎖

      當我們使用范圍查詢,並請求共享或排他鎖時,InnoDB會給符合條件的所有索引項加鎖,對於在范圍內但是不存在的記錄,叫做間隙GAP,同時也會被加鎖,這個鎖叫做Next-Key鎖。Next-Key鎖時為了防止幻讀。

      當使用范圍檢索並鎖定記錄時,InnoDB會阻塞條件范圍內鍵值的並發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是並發插入比較多的應用,盡量使用相等條件來訪問更新數據,避免使用范圍條件。

 3.6 什么時候使用行級鎖

      對於InnoDB,絕大部分情況下都應該使用行級鎖,個別特殊事務中,可以考慮使用表級鎖。

      - 事務需要更新大部分或全部數據,表比較大,如果使用默認的行鎖,會造成事務執行效率低,而且可能造成其他事務長時間鎖等待和鎖沖突。這種情況考慮使用表鎖來提高事務的執行速度。

      - 事務設計多個表,比較復雜,很可能引起死鎖,造成大量事務回滾,此時可考慮使用表鎖避免死鎖,減少數據庫因事務回滾帶來的開銷。

      當然,生產環境中最好不要出現這兩種事務,否則就應該考慮MyISAM了。

      表鎖注意事項:

      - 表鎖不是由InnoDB存儲引擎管理的,而是由MySQL Server負責的,僅當autocommit=0,innodb_table_locas=1(缺省值)時,InnoDB才能知道MySQL加的表鎖,也才能感知InnoDB加的行鎖, 這種情況下,InnoDB才能自動識別涉及表級鎖的死鎖,否則InnoDB將無法自動檢測並處理這種死鎖。

      - 在用LOCK TABLES對InnoDB加鎖時,需要將AUTOCOMMIT設為0,否則不會給表加鎖;事務結束前,不要用UNLOCKS TABLES釋放表鎖,因為UNLOCK TABLES會隱含提交事務;COMMIT或ROLLBACK並不能釋放用LOCK TABLES加的表級鎖,必須用UNLOCK TABLES釋放表鎖。

        方式如下:寫表t1並從表t2讀

        SET AUTOCOMMIT = 0;

        LOCK TABLES t1 WRITE, t2 READ;

        [do something..] 

        COMMIT;

        UNLOCK TABLES;

3.7 關於死鎖

      MyISAM表鎖是一次獲得所需全部鎖,要么全部滿足,要么等待,因為不會出現死鎖。但InnoDB鎖是逐步獲得的,所以InnoDB會發生死鎖的可能。

      比如:session1正在對table_1進行 select for update(獲得tb1排他鎖) ,此時session2對table_2進行select for update(獲得tb2排他鎖),如果此時session1對tb2進行select for update,會出現等待,直到session2釋放,但是如果session2沒有釋放並且又請求tb1進行select for update,那么此時會出現死鎖。

      發生死鎖后,InnoDB一般都能自動檢測到,然后釋放一個事務鎖並回退,另一個事務獲得鎖繼續完成事務。但在涉及外部鎖或涉及鎖的情況下,InnoDB並不能完全自動檢測到死鎖,這需要通過設置鎖超時參數innodb_lock_wait_timeout來解決,但這個參數並不是用來解決死鎖問題,在並發訪問比較高的情況下,如果大量事務因無法立即獲得所需的鎖而掛起,會拖垮數據庫,設置合適的值可以避免或者減少此種情況的發生。

      通常來說,死鎖都是應用設計的問題,通過調整業務流程、數據庫對象設計、事務大小,以及訪問數據庫的SQL語句,絕大部分死鎖都可以避免。

      避免死鎖的常用方法:

      - 程序並發存取多個表,盡量約定相同的順序來訪問量。        

      - 程序批量處理數據的時候,事先對數據排序,保證每個線程按固定的順序來處理記錄。

      - 在事務中,如果要更新記錄,應該直接申請足夠級別的排他鎖,而不是先申請共享鎖,后申請排他鎖。要不然其他事務可能已經獲得了相同的共享鎖,從而造成鎖沖突。

      - 在REPEATABLE-READ隔離級別下,如果兩個線程同時對相同條件記錄用SELECT FOR UPDATE加排他鎖,在沒有符合條件的記錄的情況下,兩個線程都會加鎖成功。因為兩個線程查詢均無此記錄,便會嘗試插入一條新紀錄,如果兩個線程都這么做,就會出現死鎖。這種情況將隔離級別改成READ COMMITTED就可避免。

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM