MySQL數據庫開發的三十六條軍規(轉)

導語
  • 來自一線的實戰經驗
  • 每一條軍規背後都是血淋淋教訓
  • 不要華麗,只要實用
  • 若有一條讓你受益,慰矣
  • 主要針對數據庫開發人員
總是在災難發生後,纔想起容災的重要性
總是在吃過虧後,才記得曾經有人提醒過
 
目錄
一,核心軍規(5)
二,字段類軍規(6)
三,索引類軍規(5)
四,SQL類軍規(15)
五,約定類軍規(5)
 
一,核心軍規
儘量不在數據庫做運算
  • 別讓腳趾頭想事情
  • 那是腦瓜子的職責
  • 讓數據庫多做她擅長的事情
    • 儘量不在數據庫做運算
    • 複雜運算移到程序端CPU
    • 儘可能簡單應用MySQL
  • 舉例:md5() / order by rand()
控制單表數據量
  • 一年內的單表數據量預估
    • 純INT不超過1000w
    • 含CHAR不超過500w
  • 合理分表不超載
    • USERID
    • DATE
    • AREA
    • ...
  • 建議單庫不超過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連接短小精悍
    • 事務/連接使用原則:即開即用,用完即關
    • 與事務無關操作放到事務外面,減少鎖資源的佔用
    • 不破壞一致性前提下,使用多個短事務代替長事務
  • 舉例
    • 發帖時的圖片上傳等待
    • 大量的sleep連接
儘可能避免使用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等
  • 米麪%前綴模糊查詢
    • B+ tree
    • 使用不了索引
    • 導致全表掃描
  • 舉例
    • 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原理:
    • limit 10000,10
    • 偏移量越大則越慢
  • 推薦分頁:
    • 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
    • UNION有去重開銷
  • 舉例
    • 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); //可能導致複製異常
永遠不在程序端顯式加鎖
  • 永遠不在程序端對數據庫顯式加鎖
    • 外部鎖對數據庫不可控
    • 高併發時是災難
    • 極難調試和排查
  • 併發扣款等一致性問題
    • 採用事務
    • 相對值修改
    • commit前二次校驗衝突
統一字符集爲UTF8
  • 字符集
    • MySQL4.1以前只有latin1
    • 爲多語言支持增加多字符集
    • 也帶來N多問題
    • 保持簡單
  • 統一字符集:UTF8
  • 校對規則:utf8_general_ci
  • 亂碼:set names utf8
統一命名規範
  • 庫表等名稱統一用小寫
    • linux VS windows
    • MySQL庫表大小寫敏感
    • 字段名大小寫不敏感
  • 索引命名默認爲"idx_字段名"
  • 庫名用縮寫,儘量在2~7個字母
    • datasharing==>ds
  • 注意避免用保留字命名
  • ...
約定類軍規小些
  • 隔離線上線下
  • 禁止未經DBA確認的子查詢上線
  • 永遠不在程序端顯式加鎖
  • 統一字符集爲UTF8
  • 統一命名規範
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章