MySQL數據庫開發的三十六條軍規(轉)
導語
- 來自一線的實戰經驗
- 每一條軍規背後都是血淋淋教訓
- 不要華麗,只要實用
- 如有一條讓你受益,慰矣
- 主要針對數據庫開發人員
老是在災難發生後,纔想起容災的重要性
老是在吃過虧後,才記得曾經有人提醒過
目錄
一,核心軍規(5)
二,字段類軍規(6)
三,索引類軍規(5)
四,SQL類軍規(15)
五,約定類軍規(5)
一,核心軍規
儘可能不在數據庫作運算
- 別讓腳趾頭想事情
- 那是腦瓜子的職責
- 讓數據庫多作她擅長的事情
-
- 儘可能不在數據庫作運算
- 複雜運算移到程序端CPU
- 儘量簡單應用MySQL
- 舉例:md5() / order by rand()
控制單表數據量
- 一年內的單表數據量預估
-
- 純INT不超過1000w
- 含CHAR不超過500w
- 合理分表不超載
-
- 建議單庫不超過300-400個表
保持表身段苗條
- 表字段數少而精
-
- IO高效,全表遍歷,表修復快,提升併發,alter table快
- 單表多少字段合適?
- 單表1G體積 500w行評估
-
- 順序讀1G文件需N秒
- 單行不超過200byte
- 單表不超過50個純INT字段
- 單表不超過20個CHAR(10)字段
- 單表字段數上線控制在20-50個
平衡範式與冗餘
- 平衡是門藝術
-
- 嚴格遵循三大範式?
- 效率優先、提高性能
- 沒有絕對的對與錯
- 適當時犧牲範式,加入冗餘
- 但會增長代碼複雜度
拒絕3B
- 數據庫併發像城市交通
-
- 拒絕3B
-
- 大SQL(Big SQL)
- 大事務(Big transaction)
- 大批量(Big batch)
- 詳細解析見後
核心軍規小結
- 儘可能不在數據庫作運算
- 控制單表數據量
- 保持表身段苗條
- 平衡範式與冗餘
- 拒絕3B
二,字段類軍規
用好數值字段類型
- 三類數值類型
-
- TINYINT(1 byte)
- SMALLINT(2B)
- MEDIUMINT(3B)
- INT(4B), BIGINT(8B)
- FLOAT(4B), DOUBLE(8B)
- DECIMAL(M,D)
將字符轉化爲數字
- 數字型VS字符串型索引
-
- 舉例:用無符號INT存儲IP,而非CHAR(15)
-
- INT UNSIGNED
- INET_ATON()
- INET_NTOA()
優先使用ENUM或SET
- 優先使用ENUM或SET
-
- 存儲
-
- ENUM佔用1字節,轉爲數值運算
- SET視節點定,最多佔用8字節
- 比較時須要加'單引號(即便是數值)
- 舉例
-
- `sex` enum('F','M') COMMENT '性別'
- `c1` enum('0','1','2','3') COMMENT '職介審覈'
避免使用NULL字段
- 避免使用NULL字段
-
- 很難進行查詢優化
- NULL列加索引,須要額外空間
- 含NULL符合索引無效
- 舉例
-
- `a` char(32) DEFAULT NULL
- `b` int(10) NOT NULL
- `c` int(10) NOT NULL DEFAULT 0
少用並拆分TEXT/BLOB
- TEXT類型處理性能遠低於VARCHAR
-
- 強制生成硬盤臨時表
- 浪費更多空間
- VARCHAR(65535)==>64k(注意UTF-8)
- 儘可能不用TEXT/BLOB數據類型
- 若必須使用則拆分到單獨的表
- 舉例:CREATE TABLE t1( id INT NOT NULL AUTO_INCREMENT, data text NOT NULL, PRIMARY KEY(id))ENGINE=innodb;
不在數據庫裏存圖片
字段類軍規小結
- 用好數值字段類型
- 將字符轉化爲數字
- 優先使用枚舉ENUM/SET
- 避免使用NULL字段
- 少用並拆分TEXT/BLOB
- 不在數據庫裏存圖片
三,索引類軍規
謹慎合理添加索引
- 謹慎合理添加索引
-
- 能不加的索引儘可能不加
-
- 綜合評估數據密度和數據分佈
- 最好不超過字段數20%
- 結合核心SQL優先考慮覆蓋索引
- 舉例
-
字符字段必須建前綴索引
- 區分度
-
- 單字母區分度:26
- 4字母區分度:26*26*26*26=456,,976
- 5字母區分度:26^5=11,881,376
- 6字母區分度:26^6=308,915,776
- 字符字段必須建前綴索引
-
- `pinyin` varchar(100) default null comment '小區拼音', key `idx_pinyin`(`pinyin`(8))) engine=innodb
- 不在索引列進行數學運算或函數運算
-
- 舉例
-
- BAD:select * from table where to_days(current_date)-to_days(date_col)<=10
- GOOD:select * from table where date_col>=date_sub('2011-10-22',interval 10 day)
自增列或全局ID作INNODB主鍵
- 對主鍵創建聚簇索引
- 而建索引存儲主鍵值
- 主鍵不該更新修改
- 案子增順序插入值
- 忌用字符串作主鍵
- 聚簇索引分裂
- 推薦用獨立於業務的AUTO_INCREMENT列或全局ID生成器作代理主鍵
- 若不指定主鍵,InnoDB會用惟一且非空值索引代替
儘可能不用外鍵
- 線上OLTP系統(線下系統另論)
-
- 外鍵可節省開發量
- 有額外開銷
- 逐行操做
- 可’到達‘其餘表,意味着鎖
- 高併發時容易死鎖
- 由程序保證約束
索引類軍規小結
- 謹慎合理添加索引
- 字符字段必須建前綴索引
- 不在索引列作運算
- 自增列或全局ID作INNODB主鍵
- 儘可能不用外鍵
四,SQL類軍規
SQL語句儘量簡單
- 大SQL VS多個簡單SQL
-
- 傳統設計思想
- BUT MySQL NOT
- 一條SQL之恩可以在一個CPU運算
- 5000+QPS的高併發中,1秒大SQL意味着?
- 可能一條大SL就把整個數據庫堵死
- 拒絕大SQL,拆解成多條簡單SQL
-
- 簡單SQL緩存命中率更高
- 減小鎖表時間,特別是MyISAM
- 用上多CPU
保持事務(鏈接)短小
- 保持事務/DB鏈接短小精悍
-
- 事務/鏈接使用原則:即開即用,用完即關
- 與事務無關操做放到事務外面,減小鎖資源的佔用
- 不破壞一致性前提下,使用多個短事務代替長事務
- 舉例
-
儘量避免使用SP/TRIG/FUNC
- 線上OLTP系統(線下庫另論)
-
- 儘量少用存儲過程
- 儘量少用觸發器
- 減用使用MySQL函數對結果進行處理
- 由客戶端程序負責
儘可能不使用SELECT *
- 用select *時
-
- 更多消耗CPU、內存、IO、網絡帶寬
- 先向數據庫請求全部列,而後丟掉不須要列?
- 儘可能不使用select*,只取須要數據列
-
- 更安全的設計:減小表變化帶來的影響
- 爲使用covering index提供可能性
- select/join減小硬盤臨時表生成,特別是有TEXT/BLOB時
- 舉例
-
- select * from tag where id=999184
- select keyword from tag where id=999184
改寫OR爲IN()
- 同一字段,將or改成in()
-
- or效率:O(n)
- in效率:O(log n)
- 當N很大時,or會慢不少
- 注意控制IN的個數,建議n小於200
- 舉例
-
- select * from opp where phone ='123456' or hple ='1235516'
- select * from opp where phone in('123456' ,'1235516')
改寫or爲union
- 不一樣字段,將or改成union
-
- 減小對不一樣字段進行"or"查詢
- merge index每每很弱智
- 若是有足夠信心:set global optimizer_switch='index_merge=off'
- 舉例
-
- select * frmo opp where phone='010-88886666' or cellphone='13800138000'
- select * from opp where phone='010-88886666' union select * from opp where cellphone='13800138000'
避免負向查詢和%前綴模糊查詢
- 避免負向查詢
-
- not, !=, <>, !<, !>, not exists, not in, not like等
- 米麪%前綴模糊查詢
-
- 舉例
-
- MySQL> select * from post where title like '北京%';
- 298 rows in set(0.01sec)
- MySQL> select * from post where title like '%北京%';
- 572 rows in set(3.27sec)
count(*)的幾個例子
- 幾個有趣的例子:
-
- count(col) VS count(*)
- count(*) VS count(1)
- count(1) VS count(0) VS count(100)
- 結論
-
- count(*) = count(1)
- count(0) = count(1)
- count(1) = count(100)
- count(*) != count(col)
- WHY?
減小count(*)
- MyISAM VS InnoDB
-
- 不帶where count()
- 帶where count()
- count(*)的資源開銷大,儘可能不用少用
- 計數統計
-
- 實時統計:用memcache,雙向更新,凌晨跑基準
- 非實時統計:儘可能用單獨統計表,按期重算
LIMIT高效分頁
- 傳統分頁
-
- select * from table limit 10000,10;
- LIMIT原理:
-
- 推薦分頁:
-
- select * from table where id>=23423 limit 11; #10+1(每頁10條)
- select * from table where id>=23434 limit 11;
LIMIT的高效分頁
- 分頁方式二:
-
- select * from table where id >=(select id from table limit 10000,1)limit 10;
- 分頁方式三:
-
- select * from table inner join (select id from table limit 10000,1) using(id);
- 分頁方式四:
-
- 程序取ID:select id from table limit 10000,10;
- select * from table where id in(123,456,...)
- 可能需按場景分析並重組索引
LIMIT的高效分頁
- 示例
-
- MySQL> select sql_no_cache * from post limit 10,10;
- 10 row in set(0.01sec)
- MySQL> select sql_no_cache * from post limit 20000,10;
- 10 row in set(0.13sec)
- MySQL> select sql_no_cache * from post limit 80000,10;
- 10 rows in set(0.58sec)
- MySQL> select sql_no_cache id from post limit 80000,10;
- 10 rows in set(0.02sec)
- MySQL> select sql_no_cache * from post where id>=323423 limit 10;
- 10 rows in set(0.01sec)
- MySQL> select * from post where id >=(select sql_no_cache id from post limit 80000,1) limit 10;
- 10 rows in set(0.02sec)
用UNION ALL而非UNION
- 若無需對結果進行去重,則用UNION ALL
-
- 舉例
-
- MySQL> select * from detail20091128 UNION ALL
- select * from detail20110427 union all
- select * from detail20110426 union all
- select * from detail20110425 union all
- select * from detail20110424 union all
- select * from detail20110423;
分解鏈接保證高併發
- 高併發DB不建議進行兩個表以上的JOIN
- 適當分解鏈接保證高併發
-
- 可換成大量早期數據
- 使用了多個MyISAM表
- 對大表的小ID IN()
- 鏈接引用同一個表屢次
- 舉例
-
-
- MySQL> select * from tag join tag_post n tag_post.tag_id=tag.id join post on tag_post.post_id=post.id where tag.tag='二手玩具'
- ->
- MySQL> select * from tab where tag='二手玩具';
- MySQL> select * from tag_post where tag_id=1321;
- MySQL> select * from post where post.id in(123,456,314,141);
GROUP BY 去除排序
- GROUP BY實現
-
- 無需排序:order by null
- 特定排序:group by desc/asc
- 舉例
- MySQL> select phone, count(*) from post group by phone limit 1;
- 1 row in set(2.19sec)
- MySQL> select phone,count(*) from post group by phone order by null limit 1;
- 1 row in set(2.02sec)
同數據類型的列植比較
- 原則:數字對數字,字符對字符
- 數值列於字符類型比較
-
- 字符列與數值類型比較
-
同數據類型的列植比較
- 舉例:字符列與數值類型比較
-
- 字段: `remark` varchar(50) not null comment '備註,默認爲空';
- MySQL> select `id`,`gift_code` from gift where `deal_id` = 640 and remark=115127;
- 1 row in set(0.14sec)
- MySQL> select `id`,`gift_code` from pool_gift where `deal_id`=640 and remark='115127';
- 1 row in set(0.005sec)
Load data 導數據
- 批量數據快導入:
-
- 成批裝載比單行裝載更快,不須要每次刷新緩存
- 無索引時裝載比索引裝載更快
- insert values, values, values減小索引刷新
- load data比insert快約20倍
- 儘可能不用insert...select
-
打散大批量更新
- 大批量更新凌晨操做,避開高峯
- 凌晨不限制
- 白天上線默認爲100條/秒(特殊再議)
- 舉例:
-
- update post set tag=1 where id in (1,2,3);
- sleep 0.01;
- update post set tag=1 where id in (4,5,6);
- sleep 0.01;
- ...
know every SQL
- show profile
- MySQLsla
- MySQLdumpslow
- explain
- show slow log
- show processlist
- show query_response_time(percona)
SQL類軍規小結
- SQL語句儘量簡單
- 保持事務(鏈接)短小
- 儘量避免使用SP/TRIG/FUNC
- 儘可能不用select *
- 改寫or語句
- 避免負向查詢和%前綴模糊查詢
- 減小count(*)
- limit的高效分頁
- 用union all 而非union
- 分解鏈接保證高併發
- group by去除排序
- 統數據類型的列植比較
- load data導數據
- 打散大批量更新
- know every SQL
五,約定類軍規
隔離線上線下
- 構建數據庫的生態環境
-
- 原則:線上連線上,線下連線下
-
- 實時數據用real庫
- 模擬環境用sim庫
- 測試用qa庫
- 開發用dev庫
- 案例
禁止未經DBA確認的子查詢
- MySQL子查詢
-
- 大部分狀況優化較差
- 特別where彙總使用in id的子查詢
- 通常可用join改寫
- 舉例:
-
- MySQL> select * from table 1 where id in(select id from table2);MySQL> insert into table1 (select * from table2); //可能致使複製異常
永遠不在程序端顯式加鎖
- 永遠不在程序端對數據庫顯式加鎖
-
- 外部鎖對數據庫不可控
- 高併發時是災難
- 極難調試和排查
- 併發扣款等一致性問題
-
統一字符集爲UTF8
- 字符集
-
- MySQL4.1之前只有latin1
- 爲多語言支持增長多字符集
- 也帶來N多問題
- 保持簡單
- 統一字符集:UTF8
- 校對規則:utf8_general_ci
- 亂碼:set names utf8
統一命名規範
- 庫表等名稱統一用小寫
-
- linux VS windows
- MySQL庫表大小寫敏感
- 字段名大小寫不敏感
- 索引命名默認爲"idx_字段名"
- 庫名用縮寫,儘可能在2~7個字母
-
- 注意避免用保留字命名
- ...
約定類軍規小些
- 隔離線上線下
- 禁止未經DBA確認的子查詢上線
- 永遠不在程序端顯式加鎖
- 統一字符集爲UTF8
- 統一命名規範
歡迎關注本站公眾號,獲取更多信息