數據庫設計
數據庫三大范式
第一范式:1NF是對屬性的原子性約束,要求屬性(列)具有原子性,不可再分解;(只要是關系型數據庫都滿足1NF)。
第二范式:2NF是對記錄的惟一性約束,表中的記錄是唯一的, 就滿足2NF, 通常我們設計一個主鍵來實現,主鍵不能包含業務邏輯。
第三范式:3NF是對字段冗余性的約束,它要求字段沒有冗余。 沒有冗余的數據庫設計可以做到。
但是,沒有冗余的數據庫未必是最好的數據庫,有時為了提高運行效率,就必須降低范式標准,適當保留冗余數據。具體做法是: 在概念數據模型設計時遵守第三范式,降低范式標准的工作放到物理數據模型設計時考慮。
降低范式就是增加字段,允許冗余。
數據類型
數據類型的選擇原則:更簡單或者占用空間更小。
1.如果長度能夠滿足,整型盡量使用tinyint、smallint、medium_int而非int。 2.如果字符串長度確定,采用char類型。 3.如果varchar能夠滿足,不采用text類型。 4.精度要求較高的使用decimal類型,也可以使用BIGINT,比如精確兩位小數就乘以100后保存。 5盡量采用timestamp而非datetime。
類型 |
占據字節 |
描述 |
datetime |
8字節 |
'1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999 |
timestamp |
4字節 |
'1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999' |
相比datetime,timestamp占用更少的空間,以UTC的格式儲存自動轉換時區。
避免空值
MySQL中字段為NULL時依然占用空間,會使索引、索引統計更加復雜。從NULL值更新到非NULL無法做到原地更新,容易發生索引分裂影響性能。盡可能將NULL值用有意義的值代替,
也能避免SQL語句里面包含 is not null的判斷。
text類型優化
由於text字段儲存大量數據,表容量會很早漲上去,影響其他字段的查詢性能。建議抽取出來放在子表里,用業務主鍵關聯。
索引
索引的分類
普通索引:最基本的索引。
組合索引:多個字段上建立的索引,能夠加速復合查詢條件的檢索。
唯一索引:與普通索引類似,但索引列的值必須唯一,允許有空值。
組合唯一索引:列值的組合必須唯一。
主鍵索引:特殊的唯一索引,用於唯一標識數據表中的某一條記錄,不允許有空值,一般用primary key約束。
全文索引:用於海量文本的查詢,MySQL5.6之后的InnoDB和MyISAM均支持全文索引。由於查詢精度以及擴展性不佳,更多的企業選擇Elasticsearch。
索引的實現原理
數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現通常使用 B 樹及其變種 B+ 樹。在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
為表設置索引要付出代價的:一是增加了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間(因為索引也要隨之變動)。
上圖展示了一種可能的索引方式。左邊是數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也並不是一定物理相鄰的)。為了加快 Col2 的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉查找在 O(log2n)的復雜度內獲取到相應數據。
創建索引可以大大提高系統的性能。
第一,通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
第二,可以大大加快數據的檢索速度,這也是創建索引的最主要的原因。
第三,可以加速表和表之間的連接,特別是在實現數據的參考完整性方面特別有意義。
第四,在使用分組和排序子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間。
第五,通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。
也許會有人要問:增加索引有如此多的優點,為什么不對表中的每一個列創建一個索引呢?因為,增加索引也有許多不利的方面。
第一,創建索引和維護索引要耗費時間,這種時間隨着數據量的增加而增加。
第二,索引需要占物理空間,除了數據表占數據空間之外,每一個索引還要占一定的物理空間,如果要建立聚簇索引,那么需要的空間就會更大。
第三,當對表中的數據進行增加、刪除和修改的時候,索引也要動態的維護,這樣就降低了數據的維護速度。
根據數據庫的功能,可以在數據庫設計器中創建三種索引:唯一索引、主鍵索引和聚集索引。
唯一索引是不允許其中任何兩行具有相同索引值的索引。
當現有數據中存在重復的鍵值時,大多數數據庫不允許將新創建的唯一索引與表一起保存。數據庫還可能防止添加將在表中創建重復鍵值的新數據。例如,如果在 employee 表中職員的姓(lname)上創建了唯一索引,則任何兩個員工都不能同姓。
主鍵索引數據庫表經常有一列或列組合,其值唯一標識表中的每一行。該列稱為表的主鍵。在數據庫關系圖中為表定義主鍵將自動創建主鍵索引,主鍵索引是唯一索引的特定類型。該索引要求主鍵中的每個值都唯一。當在查詢中使用主鍵索引時,它還允許對數據的快速訪問。
聚集索引在聚集索引中,表中行的物理順序與鍵值的邏輯(索引)順序相同。一個表只能包含一個聚集索引。如果某索引不是聚集索引,則表中行的物理順序與鍵值的邏輯順序不匹配。與非聚集索引相比,聚集索引通常提供更快的數據訪問速度。
局部性原理與磁盤預讀
由於存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分之一,因此為了提高效率,要盡量減少磁盤 I/O。為了達到這個目的,磁盤往往不是嚴格按需讀取,
而是每次都會預讀,即使只需要一個字節,磁盤也會從這個位置開始,順序向后讀取一定長度的數據放入內存。這樣做的理論依據是計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也通常會馬上被使用。
程序運行期間所需要的數據通常比較集中。由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高 I/O 效率。預讀的長度一般為頁(page)的整倍數。
頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為4k),主存和磁盤以頁為單位交換數據。
當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向后連續讀取一頁或幾頁載入內存中,然后異常返回,程序繼續運行。
B-/+Tree 索引的性能分析
上面說過一般使用磁盤 I/O 次數評價索引結構的優劣。先從 B-Tree 分析,根據 B-Tree 的定義,可知檢索一次最多需要訪問 h 個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,
這樣每個節點只需要一次 I/O 就可以完全載入。為了達到這個目的,在實際實現 B-Tree 還需要使用如下技巧:每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁里,
加之計算機存儲分配都是按頁對齊的,就實現了一個 node 只需一次 I/O。B-Tree 中一次檢索最多需要 h-1 次 I/O(根節點常駐內存),漸進復雜度為 O(h)=O(logdN)。一般實際應用中,出度 d 是非常大的數字,
通常超過 100,因此 h 非常小(通常不超過 3)。而紅黑樹這種結構,h 明顯要深的多。由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的 I/O 漸進復雜度也為 O(h),
效率明顯比 B-Tree 差很多。綜上所述,用 B-Tree 作為索引結構效率是非常高的.
Explain執行計划
explain select * from table where table.id = 1
運行上面的sql語句后你會看到,下面的表頭信息:
table | type | possible_keys | key | key_len | ref | rows | Extra
EXPLAIN列的解釋
table 顯示這一行的數據是關於哪張表的
type
這是重要的列,顯示連接使用了何種類型。從最好到最差的連接類型為const、eq_reg、ref、range、indexhe和ALL 說明:不同連接類型的解釋(按照效率高低的順序排序) system:表只有一行:system表。這是const連接類型的特殊情況。 const :表中的一個記錄的最大值能夠匹配這個查詢(索引可以是主鍵或惟一索引)。因為只有一行,這個值實際就是常數,因為MYSQL先讀這個值然后把它當做常數來對待。 eq_ref:在連接中,MYSQL在查詢時,從前面的表中,對每一個記錄的聯合都從表中讀取一個記錄,它在查詢使用了索引為主鍵或惟一鍵的全部時使用。 ref:這個連接類型只有在查詢使用了不是惟一或主鍵的鍵或者是這些類型的部分(比如,利用最左邊前綴)時發生。對於之前的表的每一個行聯合,全部記錄都將從表中讀出。
這個類型嚴重依賴於根據索引匹配的記錄多少—越少越好。 range:這個連接類型使用索引返回一個范圍中的行,比如使用>或<查找東西時發生的情況。 index:這個連接類型對前面的表中的每一個記錄聯合進行完全掃描(比ALL更好,因為索引一般小於表數據)。 ALL:這個連接類型對於前面的每一個記錄聯合進行完全掃描,這一般比較糟糕,應該盡量避免。
possible_keys
顯示可能應用在這張表中的索引。如果為空,沒有可能的索引。可以為相關的域從WHERE語句中選擇一個合適的語句
key
實際使用的索引。如果為NULL,則沒有使用索引。很少的情況下,MYSQL會選擇優化不足的索引。這種情況下,可以在SELECT語句中使用USE INDEX(indexname)來強制使用一個索引或者用
IGNORE INDEX(indexname)來強制MYSQL忽略索引
key_len
使用的索引的長度。在不損失精確性的情況下,長度越短越好
ref
顯示索引的哪一列被使用了,如果可能的話,是一個常數
rows
MYSQL認為必須檢查的用來返回請求數據的行數
Extra
關於MYSQL如何解析查詢的額外信息。將在表4.3中討論,但這里可以看到的壞的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,結果是檢索會很慢 說明:extra列返回的描述的意義 Distinct :一旦mysql找到了與行相聯合匹配的行,就不再搜索了。 Not exists :mysql優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標准的行,就不再搜索了。 Range checked for each Record(index map:#) :沒有找到理想的索引,因此對從前面表中來的每一個行組合,mysql檢查使用哪個索引,並用它來從表中返回行。這是使用索引的最慢的連接之一。 Using filesort :看到這個的時候,查詢就需要優化了。mysql需要進行額外的步驟來發現如何對返回的行排序。它根據連接類型以及存儲排序鍵值和匹配條件的全部行的行指針來排序全部行。 Using index :列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對表的全部的請求列都是同一個索引的部分的時候。 Using temporary :看到這個的時候,查詢需要優化了。這里,mysql需要創建一個臨時表來存儲結果,這通常發生在對不同的列集進行ORDER BY上,而不是GROUP BY上。 Where used :使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,並且連接類型ALL或index,這就會發生,或者是查詢有問題。 因此,弄明白了explain語法返回的每一項結果,我們就能知道查詢大致的運行時間了,如果查詢里沒有用到索引、或者需要掃描的行過多,那么可以感到明顯的延遲。因此需要改變查詢方式或者新建索引。
mysql中的explain語法可以幫助我們改寫查詢,優化表的結構和索引的設置,從而最大地提高查詢效率。當然,在大規模數據量時,索引的建立和維護的代價也是很高的,往往需要較長的時間和較大的空間,
如果在不同的列組合上建立索引,空間的開銷會更大。因此索引最好設置在需要經常查詢的字段中。
索引的代價
占用磁盤空間,對DML(update、delete、insert)語句的效率影響,增刪改會對索引影響,因為索引要重新整理。
存儲引擎 |
允許的索引類型 |
myisam |
btree |
innodb |
btree |
memory/yeap |
Hash,btree |
那些列上適合添加索引
1.在where條件經常使用 2.該字段的內容不是唯一的幾個值 3.字段內容不是頻繁變化
查詢所用使用率
show status like ‘handler_read%’;
注意:
handler_read_key:這個值越高越好,越高表示使用索引查詢到的次數。
handler_read_rnd_next:這個值越高,說明查詢低效。
索引優化
1.分頁查詢很重要,如果查詢數據量超過30%,MYSQL不會使用索引。 2.單表索引數不超過5個、單個索引字段數不超過5個。 3.字符串可使用前綴索引,前綴長度控制在5-8個字符。 4.字段唯一性太低,增加索引沒有意義,如:是否刪除、性別。 5.合理使用覆蓋索引,如下所示: select login_name, nick_name from member where login_name = ? login_name, nick_name兩個字段建立組合索引,比login_name簡單索引要更快
SQL優化
show status
使用show status查看MySQL服務器狀態信息的常用命令
mysql數據庫啟動了多少時間 show status like 'uptime'; |
show stauts like 'com_select' show stauts like 'com_insert' ...類推 update delete(顯示數據庫的查詢,更新,添加,刪除的次數) |
show [session|global] status like .... 如果你不寫 [session|global] 默認是session 會話,指取出當前窗口的執行,如果你想看所有(從mysql 啟動到現在,則應該 global) |
顯示mysql數據庫的連接數 show status like 'connections '; |
顯示慢查詢次數 show status like 'slow_queries'; |
慢查詢
什么是慢查詢
MySQL默認10秒內沒有響應SQL結果,則為慢查詢可以去修改MySQL慢查詢默認時間。
如何修改慢查詢
--查詢慢查詢時間 show variables like 'long_query_time'; --修改慢查詢時間 set long_query_time=1; ---但是重啟mysql之后,long_query_time依然是my.ini中的值 |
如何將慢查詢定位到日志中
在默認情況下,我們的mysql不會記錄慢查詢,需要在啟動mysql時候,指定記錄慢查詢才可以 bin\mysqld.exe --safe-mode --slow-query-log [mysql5.5 可以在my.ini指定](安全模式啟動,數據庫將操作寫入日志,以備恢復) bin\mysqld.exe –log-slow-queries=D:/test.log [低版本mysql5.0可以在my.ini指定] 先關閉mysql,再啟動, 如果啟用了慢查詢日志,默認把這個文件放在 my.ini 文件中記錄的位置 #Path to the database root datadir=" C:/ProgramData/MySQL/MySQL Server 5.5/Data/"
分批處理
魚塘挖開小口子放水,水面有各種漂浮物。浮萍和樹葉總能順利通過出水口,而樹枝會擋住其他物體通過,有時還會卡住,需要人工清理。MySQL就是魚塘,最大並發數和網絡帶寬就是出水口,用戶SQL就是漂浮物。
不帶分頁參數的查詢或者影響大量數據的update和delete操作,都是樹枝,我們要把它打散分批處理,舉例說明:業務描述:更新用戶所有已過期的優惠券為不可用狀態。SQL語句:
update status=0 FROMcoupon WHERE expire_date <= #{currentDate} and status=1;如果大量優惠券需要更新為不可用狀態,執行這條SQL可能會堵死其他SQL,分批處理偽代碼如下:
int pageNo = 1; |
操作符<>優化
通常<>操作符無法使用索引,舉例如下,查詢金額不為100元的訂單:select id from orders where amount != 100;如果金額為100的訂單極少,這種數據分布嚴重不均的情況下,有可能使用索引。
鑒於這種不確定性,采用union聚合搜索結果,改寫方法如下: (select id from orders where amount > 100) union all(select id from orders where amount < 100 and amount > 0)
OR優化
在Innodb引擎下or無法使用組合索引,比如: select id,product_name from orders where mobile_no = '13421800407' or user_id = 100; OR無法命中mobileno + userid的組合索引,可采用union,如下所示: (select id,product_name from orders where mobile_no = '13421800407') union(select id,product_name from orders where user_id = 100); 此時id和product_name字段都有索引,查詢才最高效。
IN優化
IN適合主表大子表小,EXIST適合主表小子表大。由於查詢優化器的不斷升級,很多場景這兩者性能差不多一樣了。 嘗試改為join查詢,舉例如下: select id from orders where user_id in (select id from user where level = 'VIP'); 采用JOIN如下所示: select o.id from orders o left join user u on o.user_id = u.id where u.level = 'VIP';
不做列運算
通常在查詢條件列運算會導致索引失效,如下所示:查詢當日訂單 select id from order where date_format(create_time,'%Y-%m-%d') = '2019-07-01'; date_format函數會導致這個查詢無法使用索引,改寫后: select id from order where create_time between '2019-07-01 00:00:00' and '2019-07-01 23:59:59';
避免Select all
如果不查詢表中所有的列,避免使用 SELECT *,它會進行全表掃描,不能有效利用索引。
Like優化
like用於模糊查詢,舉個例子(field已建立索引): SELECT column FROM table WHERE field like '%keyword%'; 這個查詢未命中索引,換成下面的寫法: SELECT column FROM table WHERE field like 'keyword%'; 去除了前面的%查詢將會命中索引,但是產品經理一定要前后模糊匹配呢?全文索引fulltext可以嘗試一下,但Elasticsearch才是終極武器。
Join優化
join的實現是采用Nested Loop Join算法,就是通過驅動表的結果集作為基礎數據,通過該結數據作為過濾條件到下一個表中循環查詢數據,然后合並結果。如果有多個join,則將前面的結果集作為循環數據,
再次到后一個表中查詢數據。
1.驅動表和被驅動表盡可能增加查詢條件,滿足ON的條件而少用Where,用小結果集驅動大結果集。
2.被驅動表的join字段上加上索引,無法建立索引的時候,設置足夠的Join Buffer Size。
3.禁止join連接三個以上的表,嘗試增加冗余字段。
Limit優化
limit用於分頁查詢時越往后翻性能越差,解決的原則:縮小掃描范圍 ,如下所示: select * from orders order by id desc limit 100000,10 耗時0.4秒select * from orders order by id desc limit 1000000,10耗時5.2秒 先篩選出ID縮小查詢范圍,寫法如下: select * from orders where id > (select id from orders order by id desc limit 1000000, 1) order by id desc limit 0,10耗時0.5秒 如果查詢條件僅有主鍵ID,寫法如下: select id from orders where id between 1000000 and 1000010 order by id desc耗時0.3秒 如果以上方案依然很慢呢?只好用游標了,感興趣的朋友閱讀JDBC使用游標實現分頁查詢的方法;
MySQL性能
最大數據量
拋開數據量和並發數,談性能都是耍流氓 。MySQL沒有限制單表最大記錄數,它取決於操作系統對文件大小的限制。
文件系統 |
單文件大小限制 |
FAT32 |
最大4G |
NTFS |
最大64GB |
NTFS5.0 |
最大2TB |
EXT2 |
塊大小為1024字節,文件最大容量16GB;塊大小為4096字節,文件最大容量2TB |
EXT3 |
塊大小為4KB,文件最大容量為4TB |
EXT4 |
理論可以大於16TB |
《阿里巴巴Java開發手冊》提出單表行數超過500萬行或者單表容量超過2GB,才推薦分庫分表。性能由綜合因素決定,拋開業務復雜度,影響程度依次是硬件配置、MySQL配置、數據表設計、索引優化。
500萬這個值僅供參考,並非鐵律。博主曾經操作過超過4億行數據的單表,分頁查詢最新的20條記錄耗時0.6秒,SQL語句大致是
select field_1,field_2 from table where id < #{prePageMinId} order by id desc limit 20,prePageMinId是上一頁數據記錄的最小ID。雖然當時查詢速度還湊合,隨着數據不斷增長,
有朝一日必定不堪重負。分庫分表是個周期長而風險高的大活兒,應該盡可能在當前結構上優化,比如升級硬件、遷移歷史數據等等,實在沒轍了再分。
最大並發數
並發數是指同一時刻數據庫能處理多少個請求,由maxconnections和maxuserconnections決定。maxconnections是指MySQL實例的最大連接數,上限值是16384,maxuserconnections是指每個數據庫用戶的
最大連接數。MySQL會為每個連接提供緩沖區,意味着消耗更多的內存。如果連接數設置太高硬件吃不消,太低又不能充分利用硬件。一般要求兩者比值超過10%,計算方法如下: max_used_connections / max_connections * 100% = 3/100 *100% ≈ 3% 查看最大連接數與響應最大連接數: show variables like '%max_connections%';show variables like '%max_user_connections%'; 在配置文件my.cnf中修改最大連接數 [mysqld]max_connections = 100max_used_connections = 20
查詢耗時0.5秒
建議將單次查詢耗時控制在0.5秒以內,0.5秒是個經驗值,源於用戶體驗的 3秒原則 。如果用戶的操作3秒內沒有響應,將會厭煩甚至退出。響應時間=客戶端UI渲染耗時+網絡請求耗時+應用程序處理耗時+查詢數據庫耗時,
0.5秒就是留給數據庫1/6的處理時間。
實施原則
相比NoSQL數據庫,MySQL是個嬌氣脆弱的家伙。如今大家都會搞點分布式,應用程序擴容比數據庫要容易得多,所以實施原則是數據庫少干活,應用程序多干活 。
1.充分利用但不濫用索引,須知索引也消耗磁盤和CPU。
2.不推薦使用數據庫函數格式化數據,交給應用程序處理。
3.不推薦使用外鍵約束,用應用程序保證數據准確性。
4.寫多讀少的場景,不推薦使用唯一索引,用應用程序保證唯一性。
5.適當冗余字段,嘗試創建中間表,用應用程序計算中間結果,用空間換時間。
6.不允許執行極度耗時的事務,配合應用程序拆分成更小的事務。
7.預估重要數據表(比如訂單表)的負載和數據增長態勢,提前優化。
MySQL數據引擎
使用的存儲引擎 myisam / innodb/ memory myisam 存儲: 如果表對事務要求不高,同時是以查詢和添加為主的,我們考慮使用myisam存儲引擎. ,比如 bbs 中的 發帖表,回復表. INNODB 存儲: 對事務要求高,保存的數據都是重要數據,我們建議使用INNODB,比如訂單表,賬號表. MyISAM 和 INNODB的區別 1. 事務安全(MyISAM不支持事務,INNODB支持事務) 2. 查詢和添加速度(MyISAM批量插入速度快) 3. 支持全文索引(MyISAM支持全文索引,INNODB不支持全文索引) 4. 鎖機制(MyISAM時表鎖,innodb是行鎖) 5. 外鍵 MyISAM 不支持外鍵, INNODB支持外鍵. (在PHP開發中,通常不設置外鍵,通常是在程序中保證數據的一致) Memory 存儲,比如我們數據變化頻繁,不需要入庫,同時又頻繁的查詢和修改,我們考慮使用memory, 速度極快. (如果mysql重啟的話,數據就不存在了)
Myisam注意事項
如果數據庫的存儲引擎是myisam,請一定記住要定時進行碎片整理 舉例說明: create table test100(id int unsigned ,name varchar(32))engine= myisam; insert into test100 values(1,’aaaaa’); insert into test100 values(2,’bbbb’); insert into test100 values(3,’ccccc’); insert into test100 select id, name from test100; 我們應該定期對myisam進行整理 optimize table test100;
分表分庫
垂直拆分
垂直拆分就是要把表按模塊划分到不同數據庫表中(當然原則還是不破壞第三范式),這種拆分在大型網站的演變過程中是很常見的。當一個網站還在很小的時候,只有小量的人來開發和維護,各模塊和表都在一起,
當網站不斷豐富和壯大的時候,也會變成多個子系統來支撐,這時就有按模塊和功能把表划分出來的需求。其實,相對於垂直切分更進一步的是服務化改造,說得簡單就是要把原來強耦合的系統拆分成多個弱耦合的服務,
通過服務間的調用來滿足業務需求看,因此表拆出來后要通過服務的形式暴露出去,而不是直接調用不同模塊的表,淘寶在架構不斷演變過程,最重要的一環就是服務化改造,
把用戶、交易、店鋪、寶貝這些核心的概念抽取成獨立的服務,也非常有利於進行局部的優化和治理,保障核心模塊的穩定性。
垂直拆分用於分布式場景。
水平拆分
上面談到垂直切分只是把表按模塊划分到不同數據庫,但沒有解決單表大數據量的問題,而水平切分就是要把一個表按照某種規則把數據划分到不同表或數據庫里。例如像計費系統,通過按時間來划分表就比較合適,
因為系統都是處理某一時間段的數據。而像SaaS應用,通過按用戶維度來划分數據比較合適,因為用戶與用戶之間的隔離的,一般不存在處理多個用戶數據的情況,簡單的按user_id范圍來水平切分
通俗理解:水平拆分行,行數據拆分到不同表中, 垂直拆分列,表數據拆分到不同表中.
如何使用水平拆分數據庫
使用水平分割拆分表,具體根據業務需求,有的按照注冊時間、取摸、賬號規則、年份等。
1.使用取摸方式分表
首先我創建三張表 user0 / user1 /user2 , 然后我再創建 uuid表,該表的作用就是提供自增的id。
create table user0(
id int unsigned primary key ,
name varchar(32) not null default '',
pwd varchar(32) not null default '')
engine=myisam charset utf8;
create table user1(
id int unsigned primary key ,
name varchar(32) not null default '',
pwd varchar(32) not null default '')
engine=myisam charset utf8;
create table user2(
id int unsigned primary key ,
name varchar(32) not null default '',
pwd varchar(32) not null default '')
engine=myisam charset utf8;
create table uuid(id int unsigned primary key auto_increment)engine=myisam charset utf8;
2.POM文件創建一個demo項目
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
3.Service代碼
@Service publicclass UserService { @Autowired private JdbcTemplate jdbcTemplate; public String regit(String name, String pwd) { // 1.先獲取到 自定增長ID String idInsertSQL = "INSERT INTO uuid VALUES (NULL);"; jdbcTemplate.update(idInsertSQL); Long insertId = jdbcTemplate.queryForObject("select last_insert_id()", Long.class); // 2.判斷存儲表名稱 String tableName = "user" + insertId % 3; // 3.注冊數據 String insertUserSql = "INSERT INTO " + tableName + " VALUES ('" + insertId + "','" + name + "','" + pwd + "');"; System.out.println("insertUserSql:" + insertUserSql); jdbcTemplate.update(insertUserSql); return"success"; } public String get(Long id) { String tableName = "user" + id % 3; String sql = "select name from " + tableName + " where id="+id; System.out.println("SQL:" + sql); String name = jdbcTemplate.queryForObject(sql, String.class); returnname; } }
4.Controller代碼
@RestController publicclass UserController { @Autowired private UserService userService; @RequestMapping("/regit") public String regit(String name, String pwd) { returnuserService.regit(name, pwd); } @RequestMapping("/get") public String get(Long id) { String name = userService.get(id); returnname; } }
5.配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
主從復制
1.解決了哪些問題
1.數據如何不被丟失
2.備份
3.讀寫分離
4.數據庫負載均衡
5.高可用
2.服務器准備
192.168.110.177 主服務器 master 192.168.110.178 從服務器slave
2.1修改主(master)服務器
vi /etc/my.cnf 新增以下內容 server_id=177 ###服務器id log-bin=mysql-bin ###開啟日志文件
2.1.1 重啟服務器
service mysqld start
service iptables stop
2.1.2 主服務器給從服務器賬號授權
GRANT REPLICATION SLAVE ON *.* to 'mysync'@'%' identified by 'q123456'; //一般不用root帳號,“%”表示所有客戶端都可能連,只要帳號,密碼正確,此處可用具體客戶端IP代替,如192.168.145.226,加強安全。
2.1.3登錄主服務器的mysql,查詢master的狀態
show master status;
//如果結果為null,則主服務器my.cf沒有配置好.
2.2. 修改從(slave)服務器
server_id=178
log-bin=mysql-bin
binlog_do_db=test
change master to master_host='192.168.110.177',master_user='mysync',master_password='q123456',
master_log_file='mysql-bin.000002',master_log_pos=343;
2.2.1. 啟動同步
start slave
2.2.2 檢查從服務器復制功能狀態
SHOW SLAVE STATUS Slave_IO_Running: Yes //此狀態必須YES Slave_SQL_Running: Yes //此狀態必須YES
讀寫分離
讀寫分離的好處
1)分攤服務器壓力,提高機器的系統處理效率讀寫分離適用於讀遠比寫的場景,如果有一台服務器,當select很多時,update和delete會被這些select訪問中的數據堵塞,等待select結束,並發性能並不高,
而主從只負責各自的寫和讀,極大程度的緩解X鎖和S鎖爭用;假如我們有1主3從,不考慮上述1中提到的從庫單方面設置,假設現在1分鍾內有10條寫入,150條讀取。那么,1主3從相當於共計40條寫入,而讀取總數沒變,
因此平均下來每台服務器承擔了10條寫入和50條讀取(主庫不承擔讀取操作)。因此,雖然寫入沒變,但是讀取大大分攤了,提高了系統性能。另外,當讀取被分攤后,又間接提高了寫入的性能。所以,總體性能提高了,
說白了就是拿機器和帶寬換性能; 2)增加冗余,提高服務可用性,當一台數據庫服務器宕機后可以調整另外一台從庫以最快速度恢復服務s
主從復制原理
依賴於二進制日志,binary-log.二進制日志中記錄引起數據庫發生改變的語句 Insert 、delete、update、create table
MyCat
什么是 Mycat
是一個開源的分布式數據庫系統,但是因為數據庫一般都有自己的數據庫引擎,而Mycat並沒有屬於自己的獨有數據庫引擎,所有嚴格意義上說並不能算是一個完整的數據庫系統,只能說是一個在應用和數據庫之間起橋梁作用的
中間件。在Mycat中間件出現之前,MySQL主從復制集群,如果要實現讀寫分離,一般是在程序段實現,這樣就帶來了一個問題,即數據段和程序的耦合度太高,如果數據庫的地址發生了改變,那么我的程序也要進行相應的修改,
如果數據庫不小心掛掉了,則同時也意味着程序的不可用,而對於很多應用來說,並不能接受;引入Mycat中間件能很好地對程序和數據庫進行解耦,這樣,程序只需關注數據庫中間件的地址,而無需知曉底層數據庫是如何
提供服務的,大量的通用數據聚合、事務、數據源切換等工作都由中間件來處理;Mycat中間件的原理是對數據進行分片處理,從原有的一個庫,被切分為多個分片數據庫,所有的分片數據庫集群構成完成的數據庫存儲,
有點類似磁盤陣列中的RAID0.
Mycat安裝
1.創建表結構
CREATE DATABASE IF NOT EXISTS `weibo_simple`; -- ------------------------------------ -- Table structure for `t_users` 用戶表 -- ------------------------------------ DROP TABLE IF EXISTS `t_users`; CREATE TABLE `t_users` ( `user_id` varchar(64) NOT NULL COMMENT '注冊用戶ID', `user_email` varchar(64) NOT NULL COMMENT '注冊用戶郵箱', `user_password` varchar(64) NOT NULL COMMENT '注冊用戶密碼', `user_nikename` varchar(64) NOT NULL COMMENT '注冊用戶昵稱', `user_creatime` datetime NOT NULL COMMENT '注冊時間', `user_status` tinyint(1) NOT NULL COMMENT '驗證狀態 1:已驗證 0:未驗證', `user_deleteflag` tinyint(1) NOT NULL COMMENT '刪除標記 1:已刪除 0:未刪除', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ------------------------------------- -- Table structure for `t_message`微博表 -- ------------------------------------- DROP TABLE IF EXISTS `t_message`; CREATE TABLE `t_message` ( `messages_id` varchar(64) NOT NULL COMMENT '微博ID', `user_id` varchar(64) NOT NULL COMMENT '發表用戶', `messages_info` varchar(255) DEFAULT NULL COMMENT '微博內容', `messages_time` datetime DEFAULT NULL COMMENT '發布時間', `messages_commentnum` int(12) DEFAULT NULL COMMENT '評論次數', `message_deleteflag` tinyint(1) NOT NULL COMMENT '刪除標記 1:已刪除 0:未刪除', `message_viewnum` int(12) DEFAULT NULL COMMENT '被瀏覽量', PRIMARY KEY (`messages_id`), KEY `user_id` (`user_id`), CONSTRAINT `t_message_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `t_users` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.配置server.xml
<!-- 添加user --> <user name="mycat"> <property name="password">mycat</property> <property name="schemas">mycat</property> </user> <!-- 添加user --> <user name="mycat_read"> <property name="password">mycat_red</property> <property name="schemas">mycat</property> <property name="readOnly">true</property> </user>
3.配置schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://org.opencloudb/"> <!-- 與server.xml中user的schemas名一致 --> <schema name="mycat" checkSQLschema="true" sqlMaxLimit="100"> <table name="t_users" primaryKey="user_id" dataNode="dn1" rule="rule1"/> <table name="t_message" type="global" primaryKey="messages_id" dataNode="dn1" /> </schema> <dataNode name="dn1" dataHost="jdbchost" database="weibo_simple" /> <dataHost name="jdbchost" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostMaster" url="172.27.185.1:3306" user="root" password="root"> </writeHost> <writeHost host="hostSlave" url="172.27.185.2:3306" user="root" password="root"/> </dataHost> </mycat:schema>
4.配置rule.xml文件
<?xml version="1.0" encoding="UTF-8"?> <!-- - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. --> <!DOCTYPE mycat:rule SYSTEM "rule.dtd"> <mycat:rule xmlns:mycat="http://org.opencloudb/"> <tableRule name="rule1"> <rule> <columns>user_id</columns> <algorithm>func1</algorithm> </rule> </tableRule> <function name="func1" class="org.opencloudb.route.function.AutoPartitionByLong"> <property name="mapFile">autopartition-long.txt</property> </function> </mycat:rule>
5.為了更好地定位錯誤,修改log4j.xml
<level value="debug" /> // 雙擊startup_nowrap.bat開始啟動
其他數據庫
作為一名后端開發人員,務必精通作為存儲核心的MySQL或SQL Server,也要積極關注NoSQL數據庫,他們已經足夠成熟並被廣泛采用,能解決特定場景下的性能瓶頸。
分類 |
數據庫 |
特性 |
鍵值型 |
Memcache |
用於內容緩存,大量數據的高訪問負載 |
鍵值型 |
[Redis](https://redis.io/) |
用於內容緩存,比Memcache支持更多的數據類型,並能持久化數據 |
列式存儲 |
HBase |
Hadoop體系的核心數據庫,海量結構化數據存儲,大數據必備。 |
文檔型 |
MongoDb |
知名文檔型數據庫,也可以用於緩存 |
文檔型 |
CouchDB |
Apache的開源項目,專注於易用性,支持REST API |
文檔型 |
SequoiaDB |
國內知名文檔型數據庫 |
圖形 |
Neo4J |
用於社交網絡構建關系圖譜,推薦系統等 |
常見命令
1.給賬號分配權限 grant all privileges on *.* to 'root'@'172.27.185.1' identified by 'root'; 2.查詢服務器server_id SHOW VARIABLES LIKE 'server_id'