Java面試總結匯總,整理了包括Java基礎知識,集合容器,併發程式設計,JVM,常用開源框架Spring,MyBatis,數據庫,中介軟體等,包含了作爲一個Java工程師在面試中需要用到或者可能用到的絕大部分知識。歡迎大家閱讀,本人見識有限,寫的部落格難免有錯誤或者疏忽的地方,還望各位大佬指點,在此表示感激不盡。文章持續更新中…
數據儲存在記憶體
優點: 存取速度快
缺點: 數據不能永久儲存
數據儲存在檔案
優點: 數據永久儲存
缺點:1)速度比記憶體操作慢,頻繁的IO操作。2)查詢數據不方便
數據儲存在數據庫
1)數據永久儲存
2)使用SQL語句,查詢方便效率高。
3)管理數據方便
結構化查詢語言(Structured Query Language)簡稱SQL,是一種數據庫查詢語言。
作用:用於存取數據、查詢、更新和管理關係數據庫系統。
MySQL是一個關係型數據庫管理系統,由瑞典MySQL AB 公司開發,屬於 Oracle 旗下產品。MySQL 是最流行的關係型數據庫管理系統之一,在 WEB 應用方面,MySQL是最好的 RDBMS (Relational Database Management System,關係數據庫管理系統) 應用軟體之一。在Java企業級開發中非常常用,因爲 MySQL 是開源免費的,並且方便擴充套件。
第一範式:每個列都不可以再拆分。
第二範式:在第一範式的基礎上,非主鍵列完全依賴於主鍵,而不能是依賴於主鍵的一部分。
第三範式:在第二範式的基礎上,非主鍵列只依賴於主鍵,不依賴於其他非主鍵。
在設計數據庫結構的時候,要儘量遵守三範式,如果不遵守,必須有足夠的理由。比如效能。事實上我們經常會爲了效能而妥協數據庫的設計。
MySQL伺服器通過許可權表來控制使用者對數據庫的存取,許可權表存放在mysql數據庫裡,由mysql_install_db指令碼初始化。這些許可權表分別user,db,table_priv,columns_priv和host。下面 下麪分別介紹一下這些表的結構和內容:
有三種格式,statement,row和mixed。
此外,新版的MySQL中對row級別也做了一些優化,當表結構發生變化的時候,會記錄語句而不是逐行記錄。
分類 | 型別名稱 | 說明 |
---|---|---|
整數型別 | tinyInt | 很小的整數(8位元二進制) |
smallint | 小的整數(16位元二進制) | |
mediumint | 中等大小的整數(24位元二進制) | |
int(integer) | 普通大小的整數(32位元二進制) | |
小數型別 | float | 單精度浮點數 |
double | 雙精度浮點數 | |
decimal(m,d) | 壓縮嚴格的定點數 | |
日期型別 | year | YYYY 1901~2155 |
time | HH:MM:SS -838:59:59~838:59:59 | |
date | YYYY-MM-DD 1000-01-01~9999-12-3 | |
datetime | YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00~ 9999-12-31 23:59:59 | |
timestamp | YYYY-MM-DD HH:MM:SS 19700101 00:00:01 UTC~2038-01-19 03:14:07UTC | |
文字、二進制型別 | CHAR(M) | M爲0~255之間的整數 |
VARCHAR(M) | M爲0~65535之間的整數 | |
TINYBLOB | 允許長度0~255位元組 | |
BLOB | 允許長度0~65535位元組 | |
MEDIUMBLOB | 允許長度0~167772150位元組 | |
LONGBLOB | 允許長度0~4294967295位元組 | |
TINYTEXT | 允許長度0~255位元組 | |
TEXT | 允許長度0~65535位元組 | |
MEDIUMTEXT | 允許長度0~167772150位元組 | |
LONGTEXT | 允許長度0~4294967295位元組 | |
VARBINARY(M) | 允許長度0~M個位元組的變長位元組字串 | |
BINARY(M) | 允許長度0~M個位元組的定長位元組字串 |
1、整數型別
,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分別表示1位元組、2位元組、3位元組、4位元組、8位元組整數。任何整數型別都可以加上UNSIGNED屬性,表示數據是無符號的,即非負整數。
長度
:整數型別可以被指定長度,例如:INT(11)表示長度爲11的INT型別。長度在大多數場景是沒有意義的,它不會限制值的合法範圍,只會影響顯示字元的個數,而且需要和UNSIGNED ZEROFILL屬性配合使用纔有意義。
例子
,假定型別設定爲INT(5),屬性爲UNSIGNED ZEROFILL,如果使用者插入的數據爲12的話,那麼數據庫實際儲存數據爲00012。
2、實數型別
,包括FLOAT、DOUBLE、DECIMAL。
DECIMAL可以用於儲存比BIGINT還大的整型,能儲存精確的小數。
而FLOAT和DOUBLE是有取值範圍的,並支援使用標準的浮點進行近似計算。
計算時FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字串進行處理。
3、字串型別
,包括VARCHAR、CHAR、TEXT、BLOB
VARCHAR用於儲存可變長字串,它比定長型別更節省空間。
VARCHAR使用額外1或2個位元組儲存字串長度。列長度小於255位元組時,使用1位元組表示,否則使用2位元組表示。
VARCHAR儲存的內容超出設定的長度時,內容會被截斷。
CHAR是定長的,根據定義的字串長度分配足夠的空間。
CHAR會根據需要使用空格進行填充方便比較。
CHAR適合儲存很短的字串,或者所有值都接近同一個長度。
CHAR儲存的內容超出設定的長度時,內容同樣會被截斷。
使用策略:
對於經常變更的數據來說,CHAR比VARCHAR更好,因爲CHAR不容易產生碎片。
對於非常短的列,CHAR比VARCHAR在儲存空間上更有效率。
使用時要注意只分配需要的空間,更長的列排序時會消耗更多記憶體。
儘量避免使用TEXT/BLOB型別,查詢時會使用臨時表,導致嚴重的效能開銷。
4、列舉型別(ENUM)
,把不重複的數據儲存爲一個預定義的集合。
有時可以使用ENUM代替常用的字串型別。
ENUM儲存非常緊湊,會把列表值壓縮到一個或兩個位元組。
ENUM在內部儲存時,其實存的是整數。
儘量避免使用數位作爲ENUM列舉的常數,因爲容易混亂。
排序是按照內部儲存的整數
5、日期和時間型別
,儘量使用timestamp,空間效率高於datetime,
用整數儲存時間戳通常不方便處理。
如果需要儲存微妙,可以使用bigint儲存。
看到這裏,這道真題是不是就比較容易回答了。
儲存引擎Storage engine:MySQL中的數據、索引以及其他物件是如何儲存的,是一套檔案系統的實現。
常用的儲存引擎有以下:
MyISAM與InnoDB區別
MyISAM | Innodb | |
---|---|---|
儲存結構 | 每張表被存放在三個檔案:frm-表格定義、MYD(MYData)-數據檔案、MYI(MYIndex)-索引檔案 | 所有的表都儲存在同一個數據檔案中(也可能是多個檔案,或者是獨立的表空間檔案),InnoDB表的大小隻受限於操作系統檔案的大小,一般爲2GB |
儲存空間 | MyISAM可被壓縮,儲存空間較小 | InnoDB的表需要更多的記憶體和儲存,它會在主記憶體中建立其專用的緩衝池用於高速緩衝數據和索引 |
可移植性、備份及恢復 | 由於MyISAM的數據是以檔案的形式儲存,所以在跨平臺的數據轉移中會很方便。在備份和恢復時可單獨針對某個表進行操作 | 免費的方案可以是拷貝數據檔案、備份 binlog,或者用 mysqldump,在數據量達到幾十G的時候就相對痛苦了 |
檔案格式 | 數據和索引是分別儲存的,數據.MYD ,索引.MYI |
數據和索引是集中儲存的,.ibd |
記錄儲存順序 | 按記錄插入順序儲存 | 按主鍵大小有序插入 |
外來鍵 | 不支援 | 支援 |
事務 | 不支援 | 支援 |
鎖支援(鎖是避免資源爭用的一個機制 機製,MySQL鎖對使用者幾乎是透明的) | 表級鎖定 | 行級鎖定、表級鎖定,鎖定力度小併發能力高 |
SELECT | MyISAM更優 | |
INSERT、UPDATE、DELETE | InnoDB更優 | |
select count(*) | myisam更快,因爲myisam內部維護了一個計數器,可以直接調取。 | |
索引的實現方式 | B+樹索引,myisam 是堆表 | B+樹索引,Innodb 是索引組織表 |
雜湊索引 | 不支援 | 支援 |
全文索引 | 支援 | 不支援 |
如果沒有特別的需求,使用預設的Innodb
即可。
MyISAM:以讀寫插入爲主的應用程式,比如部落格系統、新聞入口網站。
Innodb:更新(刪除)操作頻率也高,或者要保證數據的完整性;併發量高,支援事務和外來鍵。比如OA自動化辦公系統。
索引是一種特殊的檔案(InnoDB數據表上的索引是表空間的一個組成部分),它們包含着對數據表裏所有記錄的參照指針。
索引是一種數據結構。數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現通常使用B樹及其變種B+樹。
更通俗的說,索引就相當於目錄。爲了方便查詢書中的內容,通過對內容建立索引形成目錄。索引是一個檔案,它是要佔據物理空間的。
索引的優點
索引的缺點
where
上圖中,根據id
查詢記錄,因爲id
欄位僅建立了主鍵索引,因此此SQL執行可選的索引只有主鍵索引,如果有多個,最終會選一個較優的作爲檢索的依據。
-- 增加一個沒有建立索引的欄位
alter table innodb1 add sex char(1);
-- 按sex檢索時可選的索引爲null
EXPLAIN SELECT * from innodb1 where sex='男';
1234
可以嘗試在一個欄位未建立索引時,根據該欄位查詢的效率,然後對該欄位建立索引(
alter table 表名 add index(欄位名)
),同樣的SQL執行的效率,你會發現查詢效率會有明顯的提升(數據量越大越明顯)。
order by
當我們使用order by
將查詢結果按照某個欄位排序時,如果該欄位沒有建立索引,那麼執行計劃會將查詢出的所有數據使用外部排序(將數據從硬碟分批讀取到記憶體使用內部排序,最後合併排序結果),這個操作是很影響效能的,因爲需要將查詢涉及到的所有數據從磁碟中讀到記憶體(如果單條數據過大或者數據量過多都會降低效率),更無論讀到記憶體之後的排序了。
但是如果我們對該欄位建立索引alter table 表名 add index(欄位名)
,那麼由於索引本身是有序的,因此直接按照索引的順序和對映關係逐條取出數據即可。而且如果分頁的,那麼只用取出索引表某個範圍內的索引對應的數據,而不用像上述那取出所有數據進行排序再返回某個範圍內的數據。(從磁碟取數據是最影響效能的)
join
對
join
語句匹配關係(on
)涉及的欄位建立索引能夠提高效率
索引覆蓋
如果要查詢的欄位都建立過索引,那麼引擎會直接在索引表中查詢而不會存取原始數據(否則只要有一個欄位沒有建立索引就會做全表掃描),這叫索引覆蓋。因此我們需要儘可能的在select
後只寫必要的查詢欄位,以增加索引覆蓋的機率。
這裏值得注意的是不要想着爲每個欄位建立索引,因爲優先使用索引的優勢就在於其體積小。
主鍵索引: 數據列不允許重複,不允許爲NULL,一個表只能有一個主鍵。
唯一索引: 數據列不允許重複,允許爲NULL值,一個表允許多個列建立唯一索引。
ALTER TABLE table_name ADD UNIQUE (column);
建立唯一索引ALTER TABLE table_name ADD UNIQUE (column1,column2);
建立唯一組合索引普通索引: 基本的索引型別,沒有唯一性的限制,允許爲NULL值。
ALTER TABLE table_name ADD INDEX index_name (column);
建立普通索引ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
建立組合索引全文索引: 是目前搜尋引擎使用的一種關鍵技術。
ALTER TABLE table_name ADD FULLTEXT (column);
建立全文索引索引的數據結構和具體儲存引擎的實現有關,在MySQL中使用較多的索引有Hash索引,B+樹索引等,而我們經常使用的InnoDB儲存引擎的預設索引實現爲:B+樹索引。對於雜湊索引來說,底層的數據結構就是雜湊表,因此在絕大多數需求爲單條記錄查詢的時候,可以選擇雜湊索引,查詢效能最快;其餘大部分場景,建議選擇BTree索引。
1)B樹索引
mysql通過儲存引擎取數據,基本上90%的人用的就是InnoDB了,按照實現方式分,InnoDB的索引型別目前只有兩種:BTREE(B樹)索引和HASH索引。B樹索引是Mysql數據庫中使用最頻繁的索引型別,基本所有儲存引擎都支援BTree索引。通常我們說的索引不出意外指的就是(B樹)索引(實際是用B+樹實現的,因爲在檢視錶索引時,mysql一律列印BTREE,所以簡稱爲B樹索引)
查詢方式:
主鍵索引區:PI(關聯儲存的時數據的地址)按主鍵查詢,
普通索引區:si(關聯的id的地址,然後再到達上面的地址)。所以按主鍵查詢,速度最快
B+tree性質:
1.)n棵子tree的節點包含n個關鍵字,不用來儲存數據而是儲存數據的索引。
2.)所有的葉子結點中包含了全部關鍵字的資訊,及指向含這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大順序鏈接。
3.)所有的非終端結點可以看成是索引部分,結點中僅含其子樹中的最大(或最小)關鍵字。
4.)B+ 樹中,數據物件的插入和刪除僅在葉節點上進行。
5.)B+樹有2個頭指針,一個是樹的根節點,一個是最小關鍵碼的葉節點。
2)雜湊索引
簡要說下,類似於數據結構中簡單實現的HASH表(雜湊表)一樣,當我們在mysql中用雜湊索引時,主要就是通過Hash演算法(常見的Hash演算法有直接定址法、平方取中法、摺疊法、除數取餘法、亂數法),將數據庫欄位數據轉換成定長的Hash值,與這條數據的行指針一併存入Hash表的對應位置;如果發生Hash碰撞(兩個不同關鍵字的Hash值相同),則在對應Hash鍵下以鏈表形式儲存。當然這只是簡略模擬圖。
索參照來快速地尋找那些具有特定值的記錄。如果沒有索引,一般來說執行查詢時遍歷整張表。
索引的原理很簡單,就是把無序的數據變成有序的查詢
索引演算法有 BTree演算法和Hash演算法
BTree演算法
BTree是最常用的mysql數據庫索引演算法,也是mysql預設的演算法。因爲它不僅可以被用在=,>,>=,<,<=和between這些比較操作符上,而且還可以用於like操作符,只要它的查詢條件是一個不以萬用字元開頭的常數, 例如:
-- 只要它的查詢條件是一個不以萬用字元開頭的常數
select * from user where name like 'jack%';
-- 如果一萬用字元開頭,或者沒有使用常數,則不會使用索引,例如:
select * from user where name like '%jack';
1234
Hash演算法
Hash Hash索引只能用於對等比較,例如=,<=>(相當於=)操作符。由於是一次定位數據,不像BTree索引需要從根節點到枝節點,最後才能 纔能存取到頁節點這樣多次IO存取,所以檢索效率遠高於BTree索引。
索引雖好,但也不是無限制的使用,最好符合一下幾個原則
1) 最左字首匹配原則,組合索引非常重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。
2)較頻繁作爲查詢條件的欄位纔去建立索引
3)更新頻繁欄位不適合建立索引
4)若是不能有效區分數據的列不適合做索引列(如性別,男女未知,最多也就三種,區分度實在太低)
5)儘量的擴充套件索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。
6)定義有外來鍵的數據列一定要建立索引。
7)對於那些查詢中很少涉及的列,重複值比較多的列不要建立索引。
8)對於定義爲text、image和bit的數據型別的列不要建立索引。
第一種方式:在執行CREATE TABLE時建立索引
CREATE TABLE user_index2 (
id INT auto_increment PRIMARY KEY,
first_name VARCHAR (16),
last_name VARCHAR (16),
id_card VARCHAR (18),
information text,
KEY name (first_name, last_name),
FULLTEXT KEY (information),
UNIQUE KEY (id_card)
);
12345678910
第二種方式:使用ALTER TABLE命令去增加索引
ALTER TABLE table_name ADD INDEX index_name (column_list);
1
ALTER TABLE用來建立普通索引、UNIQUE索引或PRIMARY KEY索引。
其中table_name是要增加索引的表名,column_list指出對哪些列進行索引,多列時各列之間用逗號分隔。
索引名index_name可自己命名,預設時,MySQL將根據第一個索引列賦一個名稱。另外,ALTER TABLE允許在單個語句中更改多個表,因此可以在同時建立多個索引。
第三種方式:使用CREATE INDEX命令建立
CREATE INDEX index_name ON table_name (column_list);
1
CREATE INDEX可對錶增加普通索引或UNIQUE索引。(但是,不能建立PRIMARY KEY索引)
刪除索引
根據索引名刪除普通索引、唯一索引、全文索引:alter table 表名 drop KEY 索引名
alter table user_index drop KEY name;
alter table user_index drop KEY id_card;
alter table user_index drop KEY information;
123
刪除主鍵索引:alter table 表名 drop primary key
(因爲主鍵只有一個)。這裏值得注意的是,如果主鍵自增長,那麼不能直接執行此操作(自增長依賴於主鍵索引):
需要取消自增長再行刪除:
alter table user_index
-- 重新定義欄位
MODIFY id int,
drop PRIMARY KEY
1234
但通常不會刪除主鍵,因爲設計主鍵一定與業務邏輯無關。
通常,通過索引查詢數據比全表掃描要快。但是我們也必須注意到它的代價。
關於索引:由於索引需要額外的維護成本,因爲索引檔案是單獨存在的檔案,所以當我們對數據的增加,修改,刪除,都會產生額外的對索引檔案的操作,這些操作需要消耗額外的IO,會降低增/改/刪的執行效率。所以,在我們刪除數據庫百萬級別數據的時候,查詢MySQL官方手冊得知刪除數據的速度和建立的索引數量是成正比的。
語法:index(field(10))
,使用欄位值的前10個字元建立索引,預設是使用欄位的全部內容建立索引。
前提:字首的標識度高。比如密碼就適合建立字首索引,因爲密碼幾乎各不相同。
實操的難度:在於字首擷取的長度。
我們可以利用select count(*)/count(distinct left(password,prefixLen));
,通過從調整prefixLen
的值(從1自增)檢視不同字首長度的一個平均匹配度,接近1時就可以了(表示一個密碼的前prefixLen
個字元幾乎能確定唯一一條記錄)
在B樹中,你可以將鍵和值存放在內部節點和葉子節點;但在B+樹中,內部節點都是鍵,沒有值,葉子節點同時存放鍵和值。
B+樹的葉子節點有一條鏈相連,而B樹的葉子節點各自獨立。
B樹可以在內部節點同時儲存鍵和值,因此,把頻繁存取的數據放在靠近根節點的地方將會大大提高熱點數據的查詢效率。這種特性使得B樹在特定數據重複多次查詢的場景中更加高效。
由於B+樹的內部節點只存放鍵,不存放值,因此,一次讀取,可以在記憶體頁中獲取更多的鍵,有利於更快地縮小查詢範圍。 B+樹的葉節點由一條鏈相連,因此,當需要進行一次全數據遍歷的時候,B+樹只需要使用O(logN)時間找到最小的一個節點,然後通過鏈進行O(N)的順序遍歷即可。而B樹則需要對樹的每一層進行遍歷,這會需要更多的記憶體置換次數,因此也就需要花費更多的時間
首先要知道Hash索引和B+樹索引的底層實現原理:
hash索引底層就是hash表,進行查詢時,呼叫一次hash函數就可以獲取到相應的鍵值,之後進行回表查詢獲得實際數據。B+樹底層實現是多路平衡查詢樹。對於每一次的查詢都是從根節點出發,查詢到葉子節點方可以獲得所查鍵值,然後根據查詢判斷是否需要回表查詢數據。
那麼可以看出他們有以下的不同:
因爲在hash索引中經過hash函數建立索引之後,索引的順序與原順序無法保持一致,不能支援範圍查詢。而B+樹的的所有節點皆遵循(左節點小於父節點,右節點大於父節點,多叉樹也類似),天然支援範圍。
因此,在大多數情況下,直接選擇B+樹索引可以獲得穩定且較好的查詢速度。而不需要使用hash索引。
在B+樹的索引中,葉子節點可能儲存了當前的key值,也可能儲存了當前的key值以及整行的數據,這就是聚簇索引和非聚簇索引。 在InnoDB中,只有主鍵索引是聚簇索引,如果沒有主鍵,則挑選一個唯一鍵建立聚簇索引。如果沒有唯一鍵,則隱式的生成一個鍵來建立聚簇索引。
當查詢使用聚簇索引時,在對應的葉子節點,可以獲取到整行數據,因此不用再次進行回表查詢。
澄清一個概念:innodb中,在聚簇索引之上建立的索引稱之爲輔助索引,輔助索引存取數據總是需要二次查詢,非聚簇索引都是輔助索引,像複合索引、字首索引、唯一索引,輔助索引葉子節點儲存的不再是行的物理位置,而是主鍵值
何時使用聚簇索引與非聚簇索引
不一定,這涉及到查詢語句所要求的欄位是否全部命中了索引,如果全部命中了索引,那麼就不必再進行回表查詢。
舉個簡單的例子,假設我們在員工表的年齡上建立了索引,那麼當進行select age from employee where age < 20
的查詢時,在索引的葉子節點上,已經包含了age資訊,不會再次進行回表查詢。
MySQL可以使用多個欄位同時建立一個索引,叫做聯合索引。在聯合索引中,如果想要命中索引,需要按照建立索引時的欄位順序挨個使用,否則無法命中索引。
具體原因爲:
MySQL使用索引時需要索引有序,假設現在建立了"name,age,school"的聯合索引,那麼索引的排序爲: 先按照name排序,如果name相同,則按照age排序,如果age的值也相等,則按照school進行排序。
當進行查詢時,此時索引僅僅按照name嚴格有序,因此必須首先使用name欄位進行等值查詢,之後對於匹配到的列而言,其按照age欄位嚴格有序,此時可以使用age欄位用做索引查詢,以此類推。因此在建立聯合索引的時候應該注意索引列的順序,一般情況下,將查詢需求頻繁或者欄位選擇性高的列放在前面。此外可以根據特例的查詢或者表結構進行單獨的調整。
事務是一個不可分割的數據庫操作序列,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另一種一致性狀態。事務是邏輯上的一組操作,要麼都執行,要麼都不執行。
事務最經典也經常被拿出來說例子就是轉賬了。
假如小明要給小紅轉賬1000元,這個轉賬會涉及到兩個關鍵操作就是:將小明的餘額減少1000元,將小紅的餘額增加1000元。萬一在這兩個操作之間突然出現錯誤比如銀行系統崩潰,導致小明餘額減少而小紅的餘額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要麼都成功,要麼都要失敗。
關係性數據庫需要遵循ACID規則,具體內容如下:
爲了達到事務的四大特性,數據庫定義了4種不同的事務隔離級別,由低到高依次爲Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可以逐個解決髒讀、不可重複讀、幻讀這幾類問題。
隔離級別 | 髒讀 | 不可重複讀 | 幻影讀 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
SQL 標準定義了四個隔離級別:
這裏需要注意的是:Mysql 預設採用的 REPEATABLE_READ隔離級別 Oracle 預設採用的 READ_COMMITTED隔離級別
事務隔離機制 機製的實現基於鎖機制 機製和併發排程。其中併發排程使用的是MVVC(多版本併發控制),通過儲存修改的舊版本資訊來支援併發一致性讀和回滾等特性。
因爲隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀取提交內容):,但是你要知道的是InnoDB 儲存引擎預設使用 **REPEATABLE-READ(可重讀)**並不會有任何效能損失。
InnoDB 儲存引擎在 分佈式事務 的情況下一般會用到**SERIALIZABLE(可序列化)**隔離級別。
當數據庫有併發事務的時候,可能會產生數據的不一致,這時候需要一些機制 機製來保證存取的次序,鎖機制 機製就是這樣的一個機制 機製。
就像酒店的房間,如果大家隨意進出,就會出現多人搶奪同一個房間的情況,而在房間上裝上鎖,申請到鑰匙的人纔可以入住並且將房間鎖起來,其他人只有等他使用完畢纔可以再次使用。
在Read Uncommitted級別下,讀取數據不需要加共用鎖,這樣就不會跟被修改的數據上的排他鎖衝突
在Read Committed級別下,讀操作需要加共用鎖,但是在語句執行完以後釋放共用鎖;
在Repeatable Read級別下,讀操作需要加共用鎖,但是在事務提交之前並不釋放共用鎖,也就是必須等待事務執行完畢以後才釋放共用鎖。
SERIALIZABLE 是限制性最強的隔離級別,因爲該級別鎖定整個範圍的鍵,並一直持有鎖,直到事務完成。
在關係型數據庫中,可以按照鎖的粒度把數據庫鎖分爲行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)和頁級鎖(BDB引擎 )。
MyISAM和InnoDB儲存引擎使用的鎖:
行級鎖,表級鎖和頁級鎖對比
行級鎖 行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少數據庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分爲共用鎖 和 排他鎖。
特點:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
表級鎖 表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支援。最常使用的MYISAM與INNODB都支援表級鎖定。表級鎖定分爲表共用讀鎖(共用鎖)與表獨佔寫鎖(排他鎖)。
特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,併發度最低。
頁級鎖 頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。
特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般
從鎖的類別上來講,有共用鎖和排他鎖。
共用鎖: 又叫做讀鎖。 當使用者要進行數據的讀取時,對數據加上共用鎖。共用鎖可以同時加上多個。
排他鎖: 又叫做寫鎖。 當使用者要進行數據的寫入時,對數據加上排他鎖。排他鎖只可以加一個,他和其他的排他鎖,共用鎖都相斥。
用上面的例子來說就是使用者的行爲有兩種,一種是來看房,多個使用者一起看房是可以接受的。 一種是真正的入住一晚,在這期間,無論是想入住的還是想看房的都不可以。
鎖的粒度取決於具體的儲存引擎,InnoDB實現了行級鎖,頁級鎖,表級鎖。
他們的加鎖開銷從大到小,併發能力也是從大到小。
答:InnoDB是基於索引來完成行鎖
例: select * from tab_with_index where id = 1 for update;
for update 可以根據條件來完成行鎖鎖定,並且 id 是有索引鍵的列,如果 id 不是索引鍵那麼InnoDB將完成表鎖,併發將無從談起
相關知識點:
死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方的資源,從而導致惡性循環的現象。
常見的解決死鎖的方法
1、如果不同程式會併發存取多個表,儘量約定以相同的順序存取表,可以大大降低死鎖機會。
2、在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖產生概率;
3、對於非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產生的概率;
如果業務處理不好可以用分佈式事務鎖或者使用樂觀鎖
數據庫管理系統(DBMS)中的併發控制的任務是確保在多個事務同時存取數據庫中同一數據時不破壞事務的隔離性和統一性以及數據庫的統一性。樂觀併發控制(樂觀鎖)和悲觀併發控制(悲觀鎖)是併發控制主要採用的技術手段。
悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反數據完整性的操作。在查詢完數據的時候就把事務鎖起來,直到提交事務。實現方式:使用數據庫中的鎖機制 機製
樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反數據完整性。在修改數據的時候把事務鎖起來,通過version的方式來進行鎖定。實現方式:樂一般會使用版本號機制 機製或CAS演算法實現。
兩種鎖的使用場景
從上面對兩種鎖的介紹,我們知道兩種鎖各有優缺點,不可認爲一種好於另一種,像樂觀鎖適用於寫比較少的情況下(多讀場景),即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。
但如果是多寫的情況,一般會經常產生衝突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了效能,所以一般多寫的場景下用悲觀鎖就比較合適。
爲了提高複雜SQL語句的複用性和表操作的安全性,MySQL數據庫管理系統提供了檢視特性。所謂檢視,本質上是一種虛擬表,在物理上是不存在的,其內容與真實的表相似,包含一系列帶有名稱的列和行數據。但是,檢視並不在數據庫中以儲存的數據值形式存在。行和列數據來自定義檢視的查詢所參照基本表,並且在具體參照檢視時動態生成。
檢視使開發者只關心感興趣的某些特定數據和所負責的特定任務,只能看到檢視中所定義的數據,而不是檢視所參照表中的數據,從而提高了數據庫中數據的安全性。
檢視的特點如下:
檢視的操作包括建立檢視,檢視檢視,刪除檢視和修改檢視。
檢視根本用途:簡化sql查詢,提高開發效率。如果說還有另外一個用途那就是相容老的表結構。
下面 下麪是檢視的常見使用場景:
效能。數據庫必須把檢視的查詢轉化成對基本表的查詢,如果這個檢視是由一個複雜的多表查詢所定義,那麼,即使是檢視的一個簡單查詢,數據庫也把它變成一個複雜的結合體,需要花費一定的時間。
修改限制。當使用者試圖修改檢視的某些行時,數據庫必須把它轉化爲對基本表的某些行的修改。事實上,當從檢視中插入或者刪除時,情況也是這樣。對於簡單檢視來說,這是很方便的,但是,對於比較複雜的檢視,可能是不可修改的
這些檢視有如下特徵:1.有UNIQUE等集合操作符的檢視。2.有GROUP BY子句的檢視。3.有諸如AVG\SUM\MAX等聚合函數的檢視。 4.使用DISTINCT關鍵字的檢視。5.連線表的檢視(其中有些例外)
遊標是系統爲使用者開設的一個數據緩衝區,存放SQL語句的執行結果,每個遊標區都有一個名字。使用者可以通過遊標逐一獲取記錄並賦給主變數,交由主語言進一步處理。
儲存過程是一個預編譯的SQL語句,優點是允許模組化的設計,就是說只需要建立一次,以後在該程式中就可以呼叫多次。如果某次操作需要執行多次SQL,使用儲存過程比單純SQL語句執行要快。
優點
1)儲存過程是預編譯過的,執行效率高。
2)儲存過程的程式碼直接存放於數據庫中,通過儲存過程名直接呼叫,減少網路通訊。
3)安全性高,執行儲存過程需要有一定許可權的使用者。
4)儲存過程可以重複使用,減少數據庫開發人員的工作量。
缺點
1)偵錯麻煩,但是用 PL/SQL Developer 偵錯很方便!彌補這個缺點。
2)移植問題,數據庫端程式碼當然是與數據庫相關的。但是如果是做工程型專案,基本不存在移植問題。
3)重新編譯問題,因爲後端程式碼是執行前編譯的,如果帶有參照關係的物件發生改變時,受影響的儲存過程、包將需要重新編譯(不過也可以設定成執行時刻自動編譯)。
4)如果在一個程式系統中大量的使用儲存過程,到程式交付使用的時候隨着使用者需求的增加會導致數據結構的變化,接着就是系統的相關問題了,最後如果使用者想維護該系統可以說是很難很難、而且代價是空前的,維護起來更麻煩。
觸發器是使用者定義在關係表上的一類由事件驅動的特殊的儲存過程。觸發器是指一段程式碼,當觸發某個事件時,自動執行這些程式碼。
使用場景
在MySQL數據庫中有如下六種觸發器:
數據定義語言DDL(Data Ddefinition Language)CREATE,DROP,ALTER
主要爲以上操作 即對邏輯結構等有操作的,其中包括表結構,檢視和索引。
數據查詢語言DQL(Data Query Language)SELECT
這個較爲好理解 即查詢操作,以select關鍵字。各種簡單查詢,連線查詢等 都屬於DQL。
數據操縱語言DML(Data Manipulation Language)INSERT,UPDATE,DELETE
主要爲以上操作 即對數據進行操作的,對應上面所說的查詢操作 DQL與DML共同構建了多數初級程式設計師常用的增刪改查操作。而查詢是較爲特殊的一種 被劃分到DQL中。
數據控制功能DCL(Data Control Language)GRANT,REVOKE,COMMIT,ROLLBACK
主要爲以上操作 即對數據庫安全性完整性等有操作的,可以簡單的理解爲許可權控制等。
SQL 約束有哪幾種?
SELECT * FROM A,B(,C)或者SELECT * FROM A CROSS JOIN B (CROSS JOIN C)#沒有任何關聯條件,結果是笛卡爾積,結果集會很大,沒有意義,很少使用內連線(INNER JOIN)SELECT * FROM A,B WHERE A.id=B.id或者SELECT * FROM A INNER JOIN B ON A.id=B.id多表中同時符合某種條件的數據記錄的集合,INNER JOIN可以縮寫爲JOIN
1
內連線分爲三類
外連線(LEFT JOIN/RIGHT JOIN)
聯合查詢(UNION與UNION ALL)
SELECT * FROM A UNION SELECT * FROM B UNION ...
1
全連線(FULL JOIN)
SELECT * FROM A LEFT JOIN B ON A.id=B.id UNIONSELECT * FROM A RIGHT JOIN B ON A.id=B.id
1
表連線面試題
有2張表,1張R、1張S,R表有ABC三列,S表有CD兩列,表中各有三條記錄。
R表
A | B | C |
---|---|---|
a1 | b1 | c1 |
a2 | b2 | c2 |
a3 | b3 | c3 |
S表
C | D |
---|---|
c1 | d1 |
c2 | d2 |
c4 | d3 |
select r.*
,s.*
from r,s
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c1 | d1 |
a3 | b3 | c3 | c1 | d1 |
a1 | b1 | c1 | c2 | d2 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 | c2 | d2 |
a1 | b1 | c1 | c4 | d3 |
a2 | b2 | c2 | c4 | d3 |
a3 | b3 | c3 | c4 | d3 |
內連線結果:
select r.*
,s.*
from r inner join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
左連線結果:
select r.*
,s.*
from r left join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 |
右連線結果:
select r.*
,s.*
from r right join s on r.c=s.c
A | B | C | C | D |
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
c4 | d3 |
全表連線的結果(MySql不支援,Oracle支援):
select r.*
,s.*
from r full join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 | ||
c4 | d3 |
-- 查詢工資最高的員工是誰?
select * from employee where salary=(select max(salary) from employee);
12
-- 查詢工資最高的員工是誰?
select * from employee where salary=(select max(salary) from employee);
12
-- 1) 查詢出2011年以後入職的員工資訊
-- 2) 查詢所有的部門資訊,與上面的虛擬表中的資訊比對,找出所有部門ID相等的員工。
select * from dept d, (select * from employee where join_date > '2011-1-1') e where e.dept_id = d.id;
-- 使用表連線:
select d.*, e.* from dept d inner join employee e on d.id = e.dept_id where e.join_date > '2011-1-1'
123456
mysql中的in語句是把外表和內表作hash 連線,而exists語句是對外表作loop回圈,每次loop回圈再對內表進行查詢。一直大家都認爲exists比in語句的效率要高,這種說法其實是不準 不準確的。這個是要區分環境的。
char的特點
varchar的特點
總之,結合效能角度(char更快)和節省磁碟空間角度(varchar更小),具體情況還需具體來設計數據庫纔是妥當的做法。
最多存放50個字元,varchar(50)和(200)儲存hello所佔空間一樣,但後者在排序時會消耗更多記憶體,因爲order by col採用fixed_length計算col長度(memory引擎也一樣)。在早期 MySQL 版本中, 50 代表位元組數,現在代表字元數。
是指顯示字元的長度。20表示最大顯示寬度爲20,但仍佔4位元組儲存,儲存範圍不變;
不影響內部儲存,只是影響帶 zerofill 定義的 int 時,前面補多少個 0,易於報表展示
對大多數應用沒有意義,只是規定一些工具用來顯示字元的個數;int(1)和int(20)儲存和計算均一樣;
int(10)的10表示顯示的數據的長度,不是儲存數據的大小;chart(10)和varchar(10)的10表示儲存數據的大小,即表示儲存多少個字元。
int(10) 10位的數據長度 9999999999,佔32個位元組,int型4位元
char(10) 10位固定字串,不足補空格 最多10個字元
varchar(10) 10位可變字串,不足補空格 最多10個字元
char(10)表示儲存定長的10個字元,不足10個就用空格補齊,佔用更多的儲存空間
varchar(10)表示儲存10個變長的字元,儲存多少個就是多少個,空格也按一個字元儲存,這一點是和char(10)的空格不同的,char(10)的空格表示佔位不算一個字元
三者都表示刪除,但是三者有一些差別:
Delete | Truncate | Drop | |
---|---|---|---|
型別 | 屬於DML | 屬於DDL | 屬於DDL |
回滾 | 可回滾 | 不可回滾 | 不可回滾 |
刪除內容 | 表結構還在,刪除表的全部或者一部分數據行 | 表結構還在,刪除表中的所有數據 | 從數據庫中刪除表,所有的數據行,索引和許可權也會被刪除 |
刪除速度 | 刪除速度慢,需要逐行刪除 | 刪除速度快 | 刪除速度最快 |
因此,在不再需要一張表的時候,用drop;在想刪除部分數據行時候,用delete;在保留表而刪除所有數據的時候用truncate。
對於低效能的SQL語句的定位,最重要也是最有效的方法就是使用執行計劃,MySQL提供了explain命令來檢視語句的執行計劃。 我們知道,不管是哪種數據庫,或者是哪種數據庫引擎,在對一條SQL語句進行執行的過程中都會做很多相關的優化,對於查詢語句,最重要的優化方式就是使用索引。 而執行計劃,就是顯示數據庫引擎對於SQL語句的執行的詳細情況,其中包含了是否使用索引,使用什麼索引,使用的索引的相關資訊等。
執行計劃包含的資訊 id 有一組數位組成。表示一個查詢中各個子查詢的執行順序;
select_type 每個子查詢的查詢型別,一些常見的查詢型別。
id | select_type | description |
---|---|---|
1 | SIMPLE | 不包含任何子查詢或union等查詢 |
2 | PRIMARY | 包含子查詢最外層查詢就顯示爲 PRIMARY |
3 | SUBQUERY | 在select或 where字句中包含的查詢 |
4 | DERIVED | from字句中包含的查詢 |
5 | UNION | 出現在union後的查詢語句中 |
6 | UNION RESULT | 從UNION中獲取結果集,例如上文的第三個例子 |
table 查詢的數據表,當從衍生表中查數據時會顯示 x 表示對應的執行計劃id partitions 表分割區、表建立的時候可以指定通過那個列進行表分割區。 舉個例子:
create table tmp (
id int unsigned not null AUTO_INCREMENT,
name varchar(255),
PRIMARY KEY (id)
) engine = innodb
partition by key (id) partitions 5;
123456
type(非常重要,可以看到有沒有走索引) 存取型別
possible_keys 可能使用的索引,注意不一定會使用。查詢涉及到的欄位上若存在索引,則該索引將被列出來。當該列爲 NULL時就要考慮當前的SQL是否需要優化了。
key 顯示MySQL在查詢中實際使用的索引,若沒有使用索引,顯示爲NULL。
TIPS:查詢中若使用了覆蓋索引(覆蓋索引:索引的數據覆蓋了需要查詢的所有數據),則該索引僅出現在key列表中
key_length 索引長度
ref 表示上述表的連線匹配條件,即哪些列或常數被用於查詢索引列上的值
rows 返回估算的結果集數目,並不是一個準確的值。
extra 的資訊非常豐富,常見的有:
【推薦】SQL效能優化的目標:至少要達到 range 級別,要求是ref級別,如果可以是consts最好。
說明:
1) consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到數據。
2) ref 指的是使用普通的索引(normal index)。
3) range 對索引進行範圍檢索。
反例:explain表的結果,type=index,索引物理檔案全掃描,速度非常慢,這個index級別比較range還低,與全表掃描是小巫見大巫。
123456
應用伺服器與數據庫伺服器建立一個連線
數據庫進程拿到請求sql
解析並生成執行計劃,執行
讀取數據到記憶體並進行邏輯處理
通過步驟一的連線,發送結果到用戶端
關掉連線,釋放資源
超大的分頁一般從兩個方向上來解決.
select * from table where age > 20 limit 1000000,10
這種查詢其實也是有可以優化的餘地的. 這條語句需要load1000000數據然後基本上全部丟棄,只取10條當然比較慢. 當時我們可以修改爲select * from table where id in (select id from table where age > 20 limit 1000000,10)
.這樣雖然也load了一百萬的數據,但是由於索引覆蓋,要查詢的所有欄位都在索引中,所以速度會很快. 同時如果ID連續的好,我們還可以select * from table where id > 1000000 limit 10
,效率也是不錯的,優化的可能性有許多種,但是核心思想都一樣,就是減少load的數據.解決超大分頁,其實主要是靠快取,可預測性的提前查到內容,快取至redis等k-V數據庫中,直接返回即可.
在阿裡巴巴《Java開發手冊》中,對超大分頁的解決辦法是類似於上面提到的第一種.
【推薦】利用延遲關聯或者子查詢優化超多分頁場景。
說明:MySQL並不是跳過offset行,而是取offset+N行,然後返回放棄前offset行,返回N行,那當offset特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行SQL改寫。
正例:先快速定位需要獲取的id段,然後再關聯:
SELECT a.* FROM 表1 a, (select id from 表1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
1234567
LIMIT 子句可以被用於強制 SELECT 語句返回指定的記錄數。LIMIT 接受一個或兩個數位參數。參數必須是一個整數常數。如果給定兩個參數,第一個參數指定第一個返回記錄行的偏移量,第二個參數指定返回記錄行的最大數目。初始記錄行的偏移量是 0(而不是 1)
mysql> SELECT * FROM table LIMIT 5,10; // 檢索記錄行 6-15
1
爲了檢索從某一個偏移量到記錄集的結束所有的記錄行,可以指定第二個參數爲 -1:
mysql> SELECT * FROM table LIMIT 95,-1; // 檢索記錄行 96-last.
1
如果只給定一個參數,它表示返回最大的記錄行數目:
mysql> SELECT * FROM table LIMIT 5; //檢索前 5 個記錄行
1
換句話說,LIMIT n 等價於 LIMIT 0,n。
用於記錄執行時間超過某個臨界值的SQL日誌,用於快速定位慢查詢,爲我們的優化做參考。
開啓慢查詢日誌
設定項:slow_query_log
可以使用show variables like ‘slov_query_log’
檢視是否開啓,如果狀態值爲OFF
,可以使用set GLOBAL slow_query_log = on
來開啓,它會在datadir
下產生一個xxx-slow.log
的檔案。
設定臨界時間
設定項:long_query_time
檢視:show VARIABLES like 'long_query_time'
,單位秒
設定:set long_query_time=0.5
實操時應該從長時間設定到短的時間,即將最慢的SQL優化掉
檢視日誌,一旦SQL超過了我們設定的臨界時間就會被記錄到xxx-slow.log
中
在業務系統中,除了使用主鍵進行的查詢,其他的我都會在測試庫上測試其耗時,慢查詢的統計主要由運維在做,會定期將業務中的慢查詢反饋給我們。
慢查詢的優化首先要搞明白慢的原因是什麼? 是查詢條件沒有命中索引?是load了不需要的數據列?還是數據量太大?
所以優化也是針對這三個方向來的,
主鍵是數據庫確保數據行在整張表唯一性的保障,即使業務上本張表沒有主鍵,也建議新增一個自增長的ID列作爲主鍵。設定了主鍵之後,在後續的刪改查的時候可能更加快速以及確保操作數據範圍安全。
推薦使用自增ID,不要使用UUID。
因爲在InnoDB儲存引擎中,主鍵索引是作爲聚簇索引存在的,也就是說,主鍵索引的B+樹葉子節點上儲存了主鍵索引以及全部的數據(按照順序),如果主鍵索引是自增ID,那麼只需要不斷向後排列即可,如果是UUID,由於到來的ID與原來的大小不確定,會造成非常多的數據插入,數據移動,然後導致產生很多的記憶體碎片,進而造成插入效能的下降。
總之,在數據量大一些的情況下,用自增主鍵效能會好一些。
關於主鍵是聚簇索引,如果沒有主鍵,InnoDB會選擇一個唯一鍵來作爲聚簇索引,如果沒有唯一鍵,會生成一個隱式的主鍵。
null值會佔用更多的位元組,且會在程式中造成很多與預期不符的情況。
密碼雜湊,鹽,使用者身份證號等固定長度的字串應該使用char而不是varchar來儲存,這樣可以節省空間且提高檢索效率。
解題方法
對於此類考題,先說明如何定位低效SQL語句,然後根據SQL語句可能低效的原因做排查,先從索引着手,如果索引沒有問題,考慮以上幾個方面,數據存取的問題,長難查詢句的問題還是一些特定型別優化的問題,逐一回答。
SQL語句優化的一些方法?
select id from t where num is null
-- 可以在num上設定預設值0,確保表中num列沒有null值,然後這樣查詢:
select id from t where num=
123
select id from t where num=10 or num=20
-- 可以這樣查詢:
select id from t where num=10 union all select id from t where num=20
123
select id from t where num in(1,2,3)
-- 對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
123
select id from t where num=@num
-- 可以改爲強制查詢使用索引:
select id from t with(index(索引名)) where num=@num
123
select id from t where num/2=100
-- 應改爲:
select id from t where num=100*2
123
select id from t where substring(name,1,3)=’abc’
-- name以abc開頭的id應改爲:
select id from t where name like ‘abc%’
123
優化原則:減少系統瓶頸,減少資源佔用,增加系統的反應速度。
一個好的數據庫設計方案對於數據庫的效能往往會起到事半功倍的效果。
需要考慮數據冗餘、查詢和更新的速度、欄位的數據型別是否合理等多方面的內容。
將欄位很多的表分解成多個表
對於欄位較多的表,如果有些欄位的使用頻率很低,可以將這些欄位分離出來形成新表。
因爲當一個表的數據量很大時,會由於使用頻率低的欄位的存在而變慢。
增加中間表
對於需要經常聯合查詢的表,可以建立中間表以提高查詢效率。
通過建立中間表,將需要通過聯合查詢的數據插入到中間表中,然後將原來的聯合查詢改爲對中間表的查詢。
增加冗餘欄位
設計數據表時應儘量遵循範式理論的規約,儘可能的減少冗餘欄位,讓數據庫設計看起來精緻、優雅。但是,合理的加入冗餘欄位可以提高查詢速度。
表的規範化程度越高,表和表之間的關係越多,需要連線查詢的情況也就越多,效能也就越差。
注意:
冗餘欄位的值在一個表中修改了,就要想辦法在其他表中更新,否則就會導致數據不一致的問題。
當 cpu 飆升到 500%時,先用操作系統命令 top 命令觀察是不是 mysqld 佔用導致的,如果不是,找出佔用高的進程,並進行相關處理。
如果是 mysqld 造成的, show processlist,看看裏面跑的 session 情況,是不是有消耗資源的 sql 在執行。找出消耗高的 sql,看看執行計劃是否準確, index 是否缺失,或者實在是數據量太大造成。
一般來說,肯定要 kill 掉這些執行緒(同時觀察 cpu 使用率是否下降),等進行相應的調整(比如說加索引、改 sql、改記憶體參數)之後,再重新跑這些 SQL。
也有可能是每個 sql 消耗資源並不多,但是突然之間,有大量的 session 連進來導致 cpu 飆升,這種情況就需要跟應用一起來分析爲何連線數會激增,再做出相應的調整,比如說限制連線數等
當MySQL單表記錄數過大時,數據庫的CRUD效能會明顯下降,一些常見的優化措施如下:
還有就是通過分庫分表的方式進行優化,主要有垂直分表和水平分表
垂直分割區:
根據數據庫裏面數據表的相關性進行拆分。 例如,使用者表中既有使用者的登錄資訊又有使用者的基本資訊,可以將使用者表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。
簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。 如下圖所示,這樣來說大家應該就更容易理解了。
垂直拆分的優點: 可以使得行數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分割區可以簡化表的結構,易於維護。
垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分割區會讓事務變得更加複雜;
把主鍵和一些列放在一個表,然後把主鍵和另外的列放在另一個表中
水平分割區:
保持數據表結構不變,通過某種策略儲存數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分佈式的目的。 水平拆分可以支撐非常大的數據量。
水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的數據拆成多張表來存放。舉個例子:我們可以將使用者資訊表拆分成多個使用者資訊表,這樣就可以避免單一表數據量過大對效能造成影響。
水品拆分可以支援非常大的數據量。需要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但由於表的數據還是在同一臺機器上,其實對於提升MySQL併發能力沒有什麼意義,所以 水平拆分最好分庫 。
水平拆分能夠 支援非常大的數據量儲存,應用端改造也少,但 分片事務難以解決 ,跨界點Join效能較差,邏輯複雜。
《Java工程師修煉之道》的作者推薦 儘量不要對數據進行分片,因爲拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的數據表在優化得當的情況下支撐千萬以下的數據量是沒有太大問題的。如果實在要分片,儘量選擇用戶端分片架構,這樣可以減少一次和中介軟體的網路I/O。
表很大,分割後可以降低在查詢時需要讀的數據和索引的頁數,同時也降低了索引的層數,提高查詢次數
下面 下麪補充一下數據庫分片的兩種常見方案:
分庫分表後面臨的問題
事務支援 分庫分表後,就成了分佈式事務了。如果依賴數據庫本身的分佈式事務管理功能去執行事務,將付出高昂的效能代價; 如果由應用程式去協助控制,形成程式邏輯上的事務,又會造成程式設計方面的負擔。
跨庫join
只要是進行切分,跨節點Join的問題是不可避免的。但是良好的設計和切分卻可以減少此類情況的發生。解決這一問題的普遍做法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求得到關聯數據。 分庫分表方案產品
跨節點的count,order by,group by以及聚合函數問題 這些是一類問題,因爲它們都需要基於全部數據集合進行計算。多數的代理都不會自動處理合併工作。解決方案:與解決跨節點join問題的類似,分別在各個節點上得到結果後在應用程式端進行合併。和join不同的是每個結點的查詢可以並行執行,因此很多時候它的速度要比單一大錶快很多。但如果結果集很大,對應用程式記憶體的消耗是一個問題。
數據遷移,容量規劃,擴容等問題 來自淘寶綜合業務平臺團隊,它利用對2的倍數取餘具有向前相容的特性(如對4取餘得1的數對2取餘也是1)來分配數據,避免了行級別的數據遷移,但是依然需要進行表級別的遷移,同時對擴容規模和分表數量都有限制。總得來說,這些方案都不是十分的理想,多多少少都存在一些缺點,這也從一個側面反映出了Sharding擴容的難度。
ID問題
一旦數據庫被切分到多個物理結點上,我們將不能再依賴數據庫自身的主鍵生成機制 機製。一方面,某個分割區數據庫自生成的ID無法保證在全域性上是唯一的;另一方面,應用程式在插入數據之前需要先獲得ID,以便進行SQL路由. 一些常見的主鍵生成策略
UUID 使用UUID作主鍵是最簡單的方案,但是缺點也是非常明顯的。由於UUID非常的長,除佔用大量儲存空間外,最主要的問題是在索引上,在建立索引和基於索引進行查詢時都存在效能問題。 Twitter的分佈式自增ID演算法Snowflake 在分佈式系統中,需要生成全域性UID的場合還是比較多的,twitter的snowflake解決了這種需求,實現也還是很簡單的,除去設定資訊,核心程式碼就是毫秒級時間41位 機器ID 10位 毫秒內序列12位元。
跨分片的排序分頁
般來講,分頁時需要按照指定欄位進行排序。當排序欄位就是分片欄位的時候,我們通過分片規則可以比較容易定位到指定的分片,而當排序欄位非分片欄位的時候,情況就會變得比較複雜了。爲了最終結果的準確性,我們需要在不同的分片節點中將數據進行排序並返回,並將不同分片返回的結果集進行彙總和再次排序,最後再返回給使用者。如下圖所示:
主從複製:將主數據庫中的DDL和DML操作通過二進制日誌(BINLOG)傳輸到從數據庫上,然後將這些日誌重新執行(重做);從而使得從數據庫的數據與主數據庫保持一致。
主從複製的作用
MySQL主從複製解決的問題
MySQL主從複製工作原理
基本原理流程,3個執行緒以及之間的關聯
主:binlog執行緒——記錄下所有改變了數據庫數據的語句,放進master上的binlog中;
從:io執行緒——在使用start slave 之後,負責從master上拉取 binlog 內容,放進自己的relay log中;
從:sql執行執行緒——執行relay log中的語句;
複製過程
Binary log:主數據庫的二進制日誌
Relay log:從伺服器的中繼日誌
第一步:master在每個事務更新數據完成之前,將該操作記錄序列地寫入到binlog檔案中。
第二步:salve開啓一個I/O Thread,該執行緒在master開啓一個普通連線,主要工作是binlog dump process。如果讀取的進度已經跟上了master,就進入睡眠狀態並等待master產生新的事件。I/O執行緒最終的目的是將這些事件寫入到中繼日誌中。
第三步:SQL Thread會讀取中繼日誌,並順序執行該日誌中的SQL事件,從而與主數據庫中的數據保持一致。
讀寫分離是依賴於主從複製,而主從複製又是爲讀寫分離服務的。因爲主從複製要求slave
不能寫只能讀(如果對slave
執行寫操作,那麼show slave status
將會呈現Slave_SQL_Running=NO
,此時你需要按照前面提到的手動同步一下slave
)。
方案一
使用mysql-proxy代理
優點:直接實現讀寫分離和負載均衡,不用修改程式碼,master和slave用一樣的帳號,mysql官方不建議實際生產中使用
缺點:降低效能, 不支援事務
方案二
使用AbstractRoutingDataSource+aop+annotation在dao層決定數據源。
如果採用了mybatis, 可以將讀寫分離放在ORM層,比如mybatis可以通過mybatis plugin攔截sql語句,所有的insert/update/delete都存取master庫,所有的select 都存取salve庫,這樣對於dao層都是透明。 plugin實現時可以通過註解或者分析語句是讀寫方法來選定主從庫。不過這樣依然有一個問題, 也就是不支援事務, 所以我們還需要重寫一下DataSourceTransactionManager, 將read-only的事務扔進讀庫, 其餘的有讀有寫的扔進寫庫。
方案三
使用AbstractRoutingDataSource+aop+annotation在service層決定數據源,可以支援事務.
缺點:類內部方法通過this.xx()方式相互呼叫時,aop不會進行攔截,需進行特殊處理。
(1)備份計劃
視庫的大小來定,一般來說 100G 內的庫,可以考慮使用 mysqldump 來做,因爲 mysqldump更加輕巧靈活,備份時間選在業務低峯期,可以每天進行都進行全量備份(mysqldump 備份出來的檔案比較小,壓縮之後更小)。
100G 以上的庫,可以考慮用 xtranbackup 來做,備份速度明顯要比 mysqldump 要快。一般是選擇一週一個全備,其餘每天進行增量備份,備份時間爲業務低峯期。
(2)備份恢復時間
物理備份恢復快,邏輯備份恢復慢
這裏跟機器,尤其是硬碟的速率有關係,以下列舉幾個僅供參考
20G的2分鐘(mysqldump)
80G的30分鐘(mysqldump)
111G的30分鐘(mysqldump)
288G的3小時(xtra)
3T的4小時(xtra)
邏輯匯入時間一般是備份時間的5倍以上
(3)備份恢復失敗如何處理
首先在恢復之前就應該做足準備工作,避免恢復的時候出錯。比如說備份之後的有效性檢查、許可權檢查、空間檢查等。如果萬一報錯,再根據報錯的提示來進行相應的調整。
(4)mysqldump和xtrabackup實現原理
mysqldump
mysqldump 屬於邏輯備份。加入–single-transaction 選項可以進行一致性備份。後臺進程會先設定 session 的事務隔離級別爲 RR(SET SESSION TRANSACTION ISOLATION LEVELREPEATABLE READ),之後顯式開啓一個事務(START TRANSACTION /*!40100 WITH CONSISTENTSNAPSHOT */),這樣就保證了該事務裡讀到的數據都是事務事務時候的快照。之後再把表的數據讀取出來。如果加上–master-data=1 的話,在剛開始的時候還會加一個數據庫的讀鎖(FLUSH TABLES WITH READ LOCK),等開啓事務後,再記錄下數據庫此時 binlog 的位置(showmaster status),馬上解鎖,再讀取表的數據。等所有的數據都已經導完,就可以結束事務
Xtrabackup:
xtrabackup 屬於物理備份,直接拷貝表空間檔案,同時不斷掃描產生的 redo 日誌並儲存下來。最後完成 innodb 的備份後,會做一個 flush engine logs 的操作(老版本在有 bug,在5.6 上不做此操作會丟數據),確保所有的 redo log 都已經落盤(涉及到事務的兩階段提交
概念,因爲 xtrabackup 並不拷貝 binlog,所以必須保證所有的 redo log 都落盤,否則可能會丟最後一組提交事務的數據)。這個時間點就是 innodb 完成備份的時間點,數據檔案雖然不是一致性的,但是有這段時間的 redo 就可以讓數據檔案達到一致性(恢復的時候做的事
情)。然後還需要 flush tables with read lock,把 myisam 等其他引擎的表給備份出來,備份完後解鎖。這樣就做到了完美的熱備。
使用 myisamchk 來修復,具體步驟:
使用repair table 或者 OPTIMIZE table命令來修復,REPAIR TABLE table_name 修復表 OPTIMIZE TABLE table_name 優化表 REPAIR TABLE 用於修復被破壞的表。 OPTIMIZE TABLE 用於回收閒置的數據庫空間,當表上的數據行被刪除時,所佔據的磁碟空間並沒有立即被回收,使用了OPTIMIZE TABLE命令後這些空間將被回收,並且對磁碟上的數據行進行重排(注意:是磁碟上,而非數據庫)