数据库:MySQL相关知识整理,值得收藏!
一、數據庫引擎
選擇:MyISAM相對簡單,所以在效率上要優于InnoDB。如果系統插入和查詢操作多,不需要事務和外鍵,選擇MyISAM,如果需要頻繁的更新、刪除操作,或者需要事務、外鍵、行級鎖的時候,選擇InnoDB。
二、事務
1、事務特性
原子性(Atomicity)
事務作為一個整體被執行 ,要么全部執行,要么全部不執行。事務執行過程中出錯,會回滾到事務開始前的狀態,所有的操作就像沒有發生一樣。
一致性(Consistency)
事務開始前和結束后,數據庫的完整性約束沒有被破壞,執行結果符合預期規則 。比如A向B轉賬,不可能A扣了錢,B卻沒收到。
隔離性(Isolation)
同一時間,只允許一個事務請求同一數據,不同的事務之間彼此沒有任何干擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉賬。
持久性(Durability)
一個事務一旦提交,對數據庫的修改應該永久保存,不能回滾。
2、事物的并發問題
臟讀
事務A讀取了事務B已經修改但尚未提交的數據。若事務B回滾數據,事務A的數據存在不一致性的問題,那么A讀取到的數據就是臟數據。【一致性】
不可重復讀
事務A在執行過程中,第一次讀取到的是原始數據,第二次讀取到的是事務B已經提交的修改后的數據。導致兩次讀取同一數據的值不一致。不符合事務的隔離性。【隔離性】
幻讀
事務A根據相同條件第二次查詢到事務B提交的新增或刪除的數據,兩次數據結果集不一致。不符合事務的隔離性。【隔離性】
小結:不可重復讀的和幻讀很容易混淆,不可重復讀側重于修改,幻讀側重于新增或刪除。解決不可重復讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表。
3、事物的隔離級別
4、總結
事務隔離級別為讀已提交時,寫數據只會鎖住相應的行。
事務隔離級別為可重復讀時,若檢索條件有索引(包括主鍵索引),默認加鎖方式是next-key 鎖【間隙鎖】;若檢索條件沒有索引,則更新數據時會鎖住整張表。一個間隙被事務加了鎖,其他事務是不能在這個間隙插入記錄的,這樣可以防止幻讀。
事務隔離級別為串行化時,讀寫數據都會鎖住整張表
隔離級別越高,越能保證數據的完整性和一致性,但是對并發性能的影響也越大。
MySQL默認隔離級別是可重復讀。
查看當前數據庫的事務隔離級別:show variables like 'tx_isolation';
MYSQL MVCC實現機制
next-key 鎖【間隙鎖】
三、鎖
1、行鎖
①. 優勢:
鎖的粒度小;
發生鎖沖突的概率低;
處理并發的能力強。
②. 劣勢:
開銷大;
加鎖慢;
會出現死鎖。
③. 加鎖方式:
自動加鎖。
對于UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及的數據集加排他鎖;對于普通SELECT語句,InnoDB不會加任何鎖;當然我們也可以顯示的加鎖:加共享鎖:select * from tableName where ... lock in share mode 加排他鎖:select * from tableName where ... for update
④. 間隙鎖【Next-Key鎖】:
當我們用范圍條件檢索數據,并請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對于鍵值在條件范圍內但并不存在的記錄,叫做"間隙(GAP)"。InnoDB也會對這個"間隙"加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。
危害(坑):若執行的條件范圍過大,則InnoDB會將整個范圍內所有的索引鍵值全部鎖定,很容易對性能造成影響。
Transaction-A mysql> update innodb_lock set k=66 where id >=6; Query OK, 1 row affected (0.63 sec) mysql> commit; Transaction-B mysql> insert into innodb_lock (id,k,v) values(7,'7','7000'); Query OK, 1 row affected (18.99 sec)⑤. 排他鎖:
排他鎖,也稱寫鎖,獨占鎖,當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
# Transaction_A mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 for update; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4000 | +----+------+------+ 1 row in set (0.00 sec) mysql> update innodb_lock set v='4001' where id=4; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.04 sec) # Transaction_B mysql> select * from innodb_lock where id=4 for update; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (9.53 sec)⑥. 共享鎖:
共享鎖,也稱讀鎖,多用于判斷數據是否存在,多個讀操作可以同時進行而不會互相影響。如果事務對讀鎖進行修改操作,很可能會造成死鎖。如下圖所示。
# Transaction_A mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 lock in share mode; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (0.00 sec) mysql> update innodb_lock set v='4002' where id=4; Query OK, 1 row affected (31.29 sec) Rows matched: 1 Changed: 1 Warnings: 0 # Transaction_B mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 lock in share mode; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (0.00 sec) mysql> update innodb_lock set v='4002' where id=4; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction⑦. 分析行鎖定:
通過檢查InnoDB_row_lock 狀態變量分析系統上的行鎖的爭奪情況,命令:
show status like 'innodb_row_lock%' mysql> show status like 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+
innodb_row_lock_current_waits: 當前正在等待鎖定的數量 innodb_row_lock_time: 從系統啟動到現在鎖定總時間長度;【非常重要的參數】 innodb_row_lock_time_avg: 每次等待所花平均時間;【非常重要的參數】 innodb_row_lock_time_max: 從系統啟動到現在等待最長的一次所花的時間;innodb_row_lock_waits: 系統啟動后到現在總共等待的次數;非常重要的參數,直接決定優化的方向和策略。
⑧. 行鎖優化:
盡可能讓所有數據檢索都通過索引來完成,避免無索引行或索引失效導致行鎖升級為表鎖。
盡可能避免間隙鎖帶來的性能下降,減少或使用合理的檢索范圍。
盡可能減少事務的粒度,比如控制事務大小,而從減少鎖定資源量和時間長度,從而減少鎖的競爭等,提供性能。
盡可能低級別事務隔離,隔離級別越高,并發的處理能力越低。
2、表鎖
①. 優勢:
開銷小;
加鎖快;
無死鎖。
②. 劣勢:
鎖粒度大;
發生鎖沖突的概率高;
并發處理能力低。
③. 加鎖方式:
自動加鎖。
查詢操作(SELECT),會自動給涉及的所有表加讀鎖,更新操作(UPDATE、DELETE、INSERT),會自動給涉及的表加寫鎖。也可以顯示加鎖:
共享讀鎖:lock table tableName read; ?獨占寫鎖:lock table tableName write; ?批量解鎖:unlock tables;
④. 共享讀鎖:
對MyISAM表的讀操作(加讀鎖),不會阻塞其他進程對同一表的讀操作,但會阻塞對同一表的寫操作。只有當讀鎖釋放后,才能執行其他進程的寫操作。
Transaction-A mysql> lock table myisam_lock read; Query OK, 0 rows affected (0.00 sec) mysql> select * from myisam_lock; 9 rows in set (0.00 sec) mysql> select * from innodb_lock; ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES mysql> update myisam_lock set v='1001' where k='1'; ERROR 1099 (HY000): Table 'myisam_lock' was locked with a READ lock and can't be updated mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) Transaction-B mysql> select * from myisam_lock; 9 rows in set (0.00 sec) mysql> select * from innodb_lock; 8 rows in set (0.01 sec) mysql> update myisam_lock set v='1001' where k='1'; Query OK, 1 row affected (18.67 sec)⑤. 獨占寫鎖:
對MyISAM表的寫操作(加寫鎖),會阻塞其他進程對同一表的讀和寫操作,只有當寫鎖釋放后,才會執行其他進程的讀寫操作。
Transaction-A mysql> set autocommit=0; Query OK, 0 rows affected (0.05 sec) mysql> lock table myisam_lock write; Query OK, 0 rows affected (0.03 sec) mysql> update myisam_lock set v='2001' where k='2'; Query OK, 1 row affected (0.00 sec) mysql> select * from myisam_lock; 9 rows in set (0.00 sec) mysql> update innodb_lock set v='1001' where k='1'; ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) Transaction-B mysql> select * from myisam_lock; 9 rows in set (42.83 sec)小結:表鎖,讀鎖會阻塞寫,不會阻塞讀。而寫鎖則會把讀、寫都阻塞。
⑥. 查看加鎖情況:
show open tables; 1表示加鎖,0表示未加鎖。
mysql> show open tables where in_use > 0; +----------+-------------+--------+-------------+ | Database | Table | In_use | Name_locked | +----------+-------------+--------+-------------+ | lock | myisam_lock | 1 | 0 | +----------+-------------+--------+-------------+⑦. 分析表鎖定:
通過檢查table_locks_waited 和 table_locks_immediate 狀態變量分析系統上的表鎖定,命令:
show status like 'table_locks%'; mysql> show status like 'table_locks%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Table_locks_immediate | 104 | | Table_locks_waited | 0 | +----------------------------+-------+table_locks_immediate: 表示立即釋放表鎖數。
table_locks_waited: 表示需要等待的表鎖數。此值越高則說明存在著越嚴重的表級鎖爭用情況。
此外,MyISAM的讀寫鎖調度是寫優先,這也是MyISAM不適合做寫為主的存儲引擎。因為寫鎖后,其他線程不能做任何操作,大量的更新會使查詢很難得到鎖,從而造成永久阻塞。
3、什么情況下用表鎖?
InnoDB默認采用行鎖,在未使用索引字段查詢時升級為表鎖。MySQL這樣設計并不是給你挖坑。它有自己的設計目的。?即便你在條件中使用了索引字段,MySQL會根據自身的執行計劃,考慮是否使用索引(所以explain命令中會有possible_key 和 key)。如果MySQL認為全表掃描效率更高,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時,別忘了檢查SQL的執行計劃,以確認是否真正使用了索引。
第一種情況:全表更新。事務需要更新大部分或全部數據,且表又比較大。若使用行鎖,會導致事務執行效率低,從而可能造成其他事務長時間鎖等待和更多的鎖沖突。
第二種情況:多表查詢。事務涉及多個表,比較復雜的關聯查詢,很可能引起死鎖,造成大量事務回滾。這種情況若能一次性鎖定事務涉及的表,從而可以避免死鎖、減少數據庫因事務回滾帶來的開銷。
4、總結
InnoDB 支持表鎖和行鎖,使用索引作為檢索條件修改數據時采用行鎖,否則采用表鎖。
InnoDB 自動給寫操作加鎖,讀操作不自動加鎖。
行鎖可能因為未使用索引而升級為表鎖,所以除了檢查索引是否創建的同時,也需要通過explain執行計劃查詢索引是否被實際使用。
行鎖相對于表鎖來說,優勢在于高并發場景下表現更突出,畢竟鎖的粒度小。
當表的大部分數據需要被修改,或者是多表復雜關聯查詢時,建議使用表鎖優于行鎖。
為了保證數據的一致完整性,任何一個數據庫都存在鎖定機制。鎖定機制的優劣直接影響到一個數據庫的并發處理能力和性能。
四、索引
1、原理
我們拿出一本新華字典,它的目錄實際上就是一種索引:非聚集索引。我們可以通過目錄迅速定位我們要查的字。而字典的內容部分一般都是按照拼音排序的,這實際上又是一種索引:聚集索引。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
2、底層實現【數據結構】
①、mysql主要使用B+樹來構建索引,為什么不用二叉樹和紅黑樹?
B+樹是多叉的,可以減少樹的高度。
索引本身較大,不會全部存儲在內存中,會以索引文件的形式存儲在磁盤上,所以索引在查找數據的過程中會涉及到磁盤I/O操作。
因磁盤I/O效率低下,mysql為了盡量減少磁盤IO的存取次數,需要利用了磁盤存取的局部性原理進行磁盤預讀。
局部性原理:為了減少磁盤IO,磁盤往往會進行數據預讀,會從某位置開始,預先順序向后讀取一定長度的數據放入內存。因為磁盤順序讀取的效率較高,不需要尋道時間,因此可以提高IO效率。
磁盤預讀長度一般為頁的整數倍,主存和磁盤以頁作為單位交換數據。當需要讀取的數據不在內存時,觸發缺頁中斷,系統會向磁盤發出讀取磁盤數據的請求,磁盤找到數據的起始位置并向后連續讀取一頁或幾頁數據載入內存,然后中斷返回,系統繼續運行。
mysql將B+數的一個節點的大小設為一個頁,這樣每個節點只需要一次I/O就可以完全載入內存【由于節點中有若干個數組,所以地址連續】。
紅黑樹的結構深度更深,很多邏輯上很近的節點(如父子節點)在物理上可能很遠,無法利用局部性原理。
在InnoDB里,每個頁默認16KB,假設索引的是8B的long型數據,每個key后有個頁號4B,還有6B的其他數據(參考《MySQL技術內幕:InnoDB存儲引擎》P193的頁面數據),那么每個頁的扇出系數為16KB/(8B+4B+6B)≈1000,即每個頁可以索引1000個key。在高度h=3時,s=1000^3=10億!!也就是說,InnoDB通過三次索引頁的I/O,即可索引10億的key。通常來說,索引樹的高度在2~4。
②. B+Tree與B-Tree的區別
M階B-Tree
定義:
樹中每個結點至多有m個孩子;
除根結點和葉子結點外,其它每個結點至少有m/2個孩子;
若根結點不是葉子結點,則至少有2個孩子;
所有葉子結點都在同一層;
特性:
關鍵字集合分布在整顆樹中;
任何一個關鍵字出現且只出現在一個結點中;
搜索有可能在非葉子結點結束;
其搜索性能等價于在關鍵字全集內做一次二分查找;
自動層次控制;
M階B+Tree
定義:
有m個子樹的節點包含有m個元素(B-Tree中是m-1);
非葉子節點不保存數據,只用于索引,所有數據都保存在葉子節點中。
所有分支節點和根節點都同時存在于子節點中,在子節點元素中是最大或者最小的元素。
葉子節點會包含所有的關鍵字,以及指向數據記錄的指針,并且葉子節點本身是根據關鍵字的大小從小到大順序鏈接。
特性:
所有關鍵字都出現在葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字是有序的;
不可能在非葉子結點命中,因為非葉子節點只有Key,沒有Data;
非葉子結點相當于是葉子結點的索引(稀疏索引),葉子結點相當于是存儲所有關鍵字數據的數據層;
更適合文件索引系統。
③. B+Tree與B-Tree的區別
B+Tree有n棵子樹的結點中含有n個關鍵字;(而B樹是n棵子樹有n-1個關鍵字)。
B+Tree所有Key(關鍵字)存儲在葉子節點,非葉子節點不存儲真正的data(數據)。
B+Tree為所有葉子節點增加了一個鏈指針,且所有葉子節點的關鍵字按從小到大順序鏈接,增強了區間訪問性。
④. 為什么mysql的索引使用B+樹而不是B樹呢?
B+樹更適合外部存儲(一般指磁盤存儲),由于內節點(非葉子節點)不存data(數據)只存Key(關鍵字),所以B+樹一個節點可以存儲更多的Key,即每個節點能索引的范圍更大更精確。也就是說使用B+樹單次磁盤I/O的信息量相比較B樹更大,I/O效率更高。
mysql是關系型數據庫,經常會按照區間來訪問某個索引列,B+樹的葉子節點間按Key的順序建立了鏈指針,加強了區間訪問性,所以B+樹對索引列上的區間范圍查詢很友好。而B樹每個節點的key和data在一起,無法進行區間查找。
3、索引優點
快速讀取數據。
唯一性索引能保證數據記錄的唯一性。
實現表與表之間的參照完整性【通過主鍵索引】。
4、索引缺點
索引需要占用物理空間。
當對表中的數據進行增加、刪除和修改的時候,索引也要動態的維護,降低了數據的維護速度。
五、mysql性能分析
1、MySQL 自身瓶頸
MySQL自身常見的性能問題有磁盤空間不足,磁盤I/O太大,服務器硬件性能低。
CPU瓶頸:CPU 飽和一般發生在數據裝入內存或從磁盤上讀取數據的時候。
IO瓶頸:磁盤I/O 瓶頸發生在裝入數據遠大于內存容量的時候。
服務器硬件的性能瓶頸:可通過top,free,iostat 和 vmstat來查看系統的性能狀態。
2、explain 分析sql語句
使用explain關鍵字可以模擬優化器執行sql查詢語句,從而得知MySQL 是如何處理sql語句。
①. id
select 查詢的序列號,包含一組可以重復的數字,表示查詢中sql語句的執行順序。一般有三種情況:第一種:id全部相同,sql的執行順序是由上至下;第二種:id全部不同,sql的執行順序是根據id大的優先執行(如果是子查詢,id的序號會遞增);第三種:id既存在相同,又存在不同的。先根據id大的優先執行,再根據相同id從上至下的執行。
②. select_type
select 查詢的類型,主要是用于區別普通查詢,聯合查詢,嵌套的復雜查詢:
simple:簡單的select 查詢,查詢中不包含子查詢或者union。
primary:查詢中若包含任何復雜的子查詢,最外層查詢則被標記為primary。
subquery:在select或where 列表中包含了子查詢。
derived:在from列表中包含的子查詢被標記為derived(衍生),MySQL會遞歸執行這些子查詢,把結果放在臨時表里。
union:若第二個select出現在union之后,則被標記為union【聯合查詢】,若union包含在from子句的子查詢中,外層select將被標記為:derived。
union result:從union表獲取結果的select。
subquery和union 還可以被標記為dependent和uncacheable。dependent意味著select依賴于外層查詢中發現的數據。uncacheable意味著select中的某些特性阻止結果被緩存于一個item_cache中。
③. table
查詢結果來自于哪個表。
④. partitions
表所使用的分區,如果要統計十年公司訂單的金額,可以把數據分為十個區,每一年代表一個區。這樣可以大大的提高查詢效率。
⑤. type
這是一個非常重要的參數,連接類型,常見的有:all , index , range , ref , eq_ref , const , system , null 八個級別。
性能從最優到最差的排序:null > system > const > eq_ref > ref > range > index > all
對java程序員來說,若保證查詢至少達到range級別或者最好能達到ref則算是一個優秀而又負責的程序員。
all:(full table scan)全表掃描無疑是最差,若是百萬千萬級數據量,全表掃描會非常慢。
index:(full index scan)全索引文件掃描比all好很多,畢竟從索引樹中找數據,比從全表中找數據要快。
range:只檢索給定范圍的行,使用索引來匹配行。范圍縮小了,當然比全表掃描和全索引文件掃描要快。sql語句中一般會有between,>,< 等查詢,IN()和OR列表,也會顯示range。
ref:非唯一性索引掃描,本質上也是一種索引訪問,返回所有匹配某個單獨值的行。比如查詢公司所有屬于研發團隊的同事,匹配的結果是多個并非唯一值。
eq_ref:唯一性索引掃描,對于每個索引鍵,表中有一條記錄與之匹配。比如用主鍵或唯一字段作為判斷條件。
const:表示通過索引一次就可以找到,const用于比較primary key 或者unique索引。因為只匹配一行數據,所以很快,若將主鍵至于where列表中,MySQL就能將該查詢轉換為一個常量。
system:表只有一條記錄(等于系統表),這是const類型的特列,平時不會出現,了解即可。
NULL:MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列里選取最小值可以通過單獨索引查找完成。
⑥. possible_keys
顯示查詢語句可能用到的索引(即查詢涉及字段中存在索引的字段,可能為一個、多個或為null),不一定被查詢實際使用,僅供參考使用。
⑦. key
顯示查詢語句實際使用的索引字段。若為null,則表示沒有使用索引。
⑧. key_len
顯示索引中使用的字節數,可通過key_len計算查詢中使用的索引長度。
在不損失精確性的情況下索引長度越短越好。key_len 顯示的值為索引字段的最可能長度,并非實際使用長度,即key_len是根據表定義計算而得,并不是通過表內檢索出的。
⑨. ref
表示上述表的連接匹配條件,即哪些列或常量被用于查找索引列上的值。即顯示使用哪個列或常數與key一起從表中選擇行。
**⑩. rows
根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數,值越大越不好。
即根據查詢語句及索引選用情況,大致估算出要得到查詢結果,所需要在表中讀取的行數。
?. filtered
一個百分比的值,和rows 列的值一起使用,可以估計出查詢執行計劃(QEP)中的前一個表的結果集,從而確定join操作的循環次數。小表驅動大表,減輕連接的次數。
?. extra
包含不適合在其他列中顯示但又十分重要的額外信息。
Using filesort:?“文件排序”,說明MySQL中無法利用索引完成的排序操作 ,MySQL會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。出現這個就要立刻優化sql。
Using temporary:使用了臨時表保存中間結果,說明MySQL在對查詢結果排序時使用臨時表。常見于排序 order by 和 分組查詢 group by時,需要借助輔助表再進行排序的情況(這種情況是多個表都涉及到排序字段才會引起的)。出現這個更要立刻優化sql。
Using index:表示相應的select 操作中使用了覆蓋索引(Covering index),避免訪問了表的數據行,效果不錯!如果同時出現Using where,表明索引被用來執行索引鍵值的查找。如果沒有同時出現Using where,表示索引只是用來讀取數據而非執行查找動作。覆蓋索引(Covering Index) :也叫索引覆蓋,就是select 的數據列只用從索引中就能夠取得,不必讀取數據行,MySQL可以利用索引返回select 列表中的字段,而不必根據索引再次讀取數據文件。
Using index condition:在5.6版本后加入的新特性,優化器會在索引存在的情況下,通過符合RANGE范圍的條數 和 總數的比例來選擇是使用索引還是進行全表遍歷。
Using where:表明使用了where 過濾。
Using join buffer:表明使用了連接緩存。
impossible where:where 語句的值總是false,不可用,不能用來獲取任何元素。
distinct:優化distinct操作,在找到第一匹配的元組后即停止找同樣值的動作。
3、explain總結
通過explain的參數介紹,我們可以得知:
sql的查詢順序(根據id,id越大越先執行)。
數據讀取操作的操作類型(type)
哪些索引被實際使用(key)
表之間的引用(ref)
每張表有多少行被優化器查詢(rows)
六、數據庫性能優化
1、優化思路
可從以下幾個方面對數據庫性能進行優化:
優化數據庫與索引的設計。
優化SQL語句。
加緩存【Memcached, Redis】
主從復制,讀寫分離。
垂直拆分,其實就是根據你模塊的耦合度,將一個包含多個字段的表分成多個小的表,將一個大的系統分為多個小的系統,也就是分布式系統。
水平切分,針對數據量大的表,這一步最麻煩,最能考驗技術水平,要選擇一個合理的sharding key,為了有好的查詢效率,表結構也要改動,做一定的冗余,應用也要改,sql中盡量帶sharding key,將數據定位到限定的表上去查,而不是掃描全部的表;
2、數據庫與索引設計
①. 數據庫設計
表字段避免null值出現,null值很難進行查詢優化且占用額外的索引空間,推薦默認數字0代替null。
盡量使用INT而非BIGINT,如果非負則加上UNSIGNED(這樣數值容量會擴大一倍),當然能使用TINYINT、SMALLINT、MEDIUM_INT更好。
使用枚舉或整數代替字符串類型。
盡量使用TIMESTAMP而非DATETIME。
單表不要有太多字段,建議在20以內。
用整型來存IP。【可去搜索IP地址轉為整型】
等。
②. 索引設計
索引要占用物理內存,并不是越多越好,要根據查詢有針對性的創建,考慮在WHERE和ORDER BY命令上涉及的列建立索引,可根據EXPLAIN來查看是否用了索引還是全表掃描。
應盡量避免在WHERE子句中對字段進行NULL值判斷,否則將導致引擎放棄使用索引而進行全表掃描。
值分布很稀少的字段不適合建索引,例如"性別"這種只有兩三個值的字段。
字符字段只建前綴索引。
字符字段最好不要做主鍵。
不用外鍵,由程序保證約束。
盡量不用UNIQUE,由程序保證約束。
使用多列索引時主意順序和查詢條件保持一致,同時刪除不必要的單列索引。
③. 總結
就一句話:使用合適的數據類型,選擇合適的索引:
使用合適的數據類型
使用可存下數據的最小的數據類型,整型 < date,time < char,varchar < blob
使用簡單的數據類型,整型比字符處理開銷更小,因為字符串的比較更復雜。如,int類型存儲時間類型,bigint類型轉ip函數。
使用合理的字段屬性長度,固定長度的表會更快。使用enum、char而不是varchar。
盡可能使用not null定義字段。
盡量少用text,非用不可最好分表。
選擇合適的索引列(即哪些列適合添加索引)
查詢頻繁的列,在where,group by,order by,on從句中出現的列。
where條件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出現的列。
長度小的列,索引字段越小越好,因為數據庫的存儲單位是頁,一頁中能存下的數據越多越好。
離散度大(不同的值多)的列,放在聯合索引前面。查看離散度,通過統計不同的列值來實現,count越大,離散程度越高。
3、sql優化
使用limit對查詢結果的記錄進行限定。
*避免select ,將需要查找的字段列出來。
使用連接(join)來代替子查詢。
拆分大的delete或insert語句。
可通過開啟慢查詢日志來找出較慢的SQL。如何開啟mysql慢查詢日志?
不做列運算:SELECT id WHERE age + 1 = 10,任何對列的操作都將導致表掃描,它包括數據庫教程函數、計算表達式等等,查詢時要盡可能將操作移至等號右邊。
sql語句盡可能簡單:一條sql只能在一個cpu運算;大語句拆小語句,減少鎖時間;一條大sql可以堵死整個庫。
OR改寫成IN:OR的效率是O(n)級別,IN的效率是O(logn)級別,in的個數建議控制在200以內。
不用函數和觸發器,在應用程序實現。
避免%xxx式查詢。
少用JOIN。
使用同類型進行比較,比如用'123'和'123'比,123和123比。
盡量避免在WHERE子句中使用 != 或 <> 操作符,否則導致引擎放棄使用索引而進行全表掃描。
對于連續數值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5。
列表數據不要拿全表,要使用LIMIT來分頁,每頁數量也不要太大。
4、分區、分庫、分表
①. 分區
把一張表的數據分成N個區塊,在邏輯上看最終只是一張表,但底層是由N個物理區塊組成的,通過將不同數據按一定規則放到不同的區塊中提升表的查詢效率。
②. 分表
水平分表:為了解決單表數據量過大(數據量達到千萬級別)問題。所以將固定的ID hash之后mod,取若0~N個值,然后將數據劃分到不同表中,需要在寫入與查詢的時候進行ID的路由與統計。
垂直分表:為了解決表的寬度問題,同時還能分別優化每張單表的處理能力。所以將表結構根據數據的活躍度拆分成多個表,把不常用的字段單獨放到一個表、把大字段單獨放到一個表、把經常使用的字段放到一個表。
③. 分庫
面對高并發的讀寫訪問,當數據庫無法承載寫操作壓力時,不管如何擴展slave服務器,此時都沒有意義了。因此需對數據庫進行拆分,從而提高數據庫寫入能力,這就是分庫。
④. 問題
事務問題。在執行分庫之后,由于數據存儲到了不同的庫上,數據庫事務管理出現了困難。如果依賴數據庫本身的分布式事務管理功能去執行事務,將付出高昂的性能代價;如果由應用程序去協助控制,形成程序邏輯上的事務,又會造成編程方面的負擔。
跨庫跨表的join問題。在執行了分庫分表之后,難以避免會將原本邏輯關聯性很強的數據劃分到不同的表、不同的庫上,我們無法join位于不同分庫的表,也無法join分表粒度不同的表,結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。
額外的數據管理負擔和數據運算壓力。額外的數據管理負擔,最顯而易見的就是數據的定位問題和數據的增刪改查的重復執行問題,這些都可以通過應用程序解決,但必然引起額外的邏輯運算。
七、緩存
對于很多的數據庫系統都能夠緩存執行計劃,對于完全相同的sql, 可以使用已經已經存在的執行計劃,從而跳過解析和生成執行計劃的過程。MYSQL提供了更為高級的查詢結果緩存功能,對于完全相同的SQL (字符串完全相同且大小寫敏感) 可以執行返回查詢結果。
MySQL緩存機制簡單的說就是緩存sql文本及查詢結果,如果運行相同的sql,服務器直接從緩存中取到結果,而不需要再去解析和執行sql。如果表更改了,那么使用這個表的所有緩沖查詢將不再有效,查詢緩存值的相關條目被清空。更改指的是表中任何數據或是結構的改變,包括INSERT、UPDATE、 DELETE、TRUNCATE(截斷)、ALTER TABLE、DROP TABLE或DROP DATABASE等,也包括那些映射到改變了的表的使用MERGE表的查詢。顯然,這對于頻繁更新的表,查詢緩存是不適合的,而對于一些不常改變數據且有 大量相同sql查詢的表,查詢緩存會節約很大的性能。
八、面試真題
問:有個表特別大,字段是姓名、年齡、班級,如果調用select * from table where name = xxx and age = xxx該如何通過建立索引的方式優化查詢速度?
答:由于mysql查詢每次只能使用一個索引,如果在name、age兩列上創建復合索引的話將帶來更高的效率。如果我們創建了(name, age)的復合索引,那么其實相當于創建了(name)、(name, age)兩個索引,這被稱為最佳左前綴特性。因此我們在創建復合索引時應該將最常用作限制條件的列放在最左邊,依次遞減。其次還要考慮該列的數據離散程度,如果有很多不同的值的話建議放在左邊,name的離散程度也大于age。
問:max(xxx)如何用索引優化?
答:在xxx列上建立索引,因為索引是B+樹順序排列的,鎖在下次查詢的時候就會使用索引來查詢到最大的值是哪個。
問:如何對分頁進行優化?
答:SELECT * FROM big_table order by xx LIMIT 1000000,20,這條語句會查詢出1000020條的所有數據然后丟棄掉前1000000條,為了避免全表掃描的操作,在order by的列上加索引就能通過掃描索引來查詢。但是這條語句會查詢還是會掃描1000020條,還能改進成select id from big_table where id >= 1000000 order by xx LIMIT 0,20,用ID作為過濾條件將不需要查詢的數據直接去除。
IT技術分享社區
個人博客網站:https://programmerblog.xyz
文章推薦程序員效率:畫流程圖常用的工具程序員效率:整理常用的在線筆記軟件遠程辦公:常用的遠程協助軟件,你都知道嗎?51單片機程序下載、ISP及串口基礎知識硬件:斷路器、接觸器、繼電器基礎知識
總結
以上是生活随笔為你收集整理的数据库:MySQL相关知识整理,值得收藏!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux操作系统启动流程简单介绍
- 下一篇: 电信路由器怎么设置虚拟服务器,电信宽带怎