MySQL开发的三十六条军规
2022-06-17 14:16:12 0 举报
AI智能生成
MySQL开发的三十六条军规
作者其他创作
大纲/内容
背景
•来自一线的实战经验<br>•每一军规背后都是血淋淋教训<br>•丌要华丽,叧要实用<br>•若有一条让你有所受益,慰矣<br>•主要针对数据库开发人员
总是在灾难发生后,才想起容灾的重要性; 总是在吃过亏后,才记得曾经有人提醒过。
一.核心军规(5)
尽量别在数据库做运算
•别让脚趾头想事情<br>•那是脑瓜子的职责
让数据库多做她擅长的事
尽量别在数据库做运算<br>复杂运算移到程序端CPU<br>尽可能简单应用MySQL
实例: md5() / Order by Rand()
控制单表数据量
一年内的单表数据量预估
纯INT不超1000W<br>含CHAR不超500W
合理分表不超载
USERID<br>DATE<br>AREA
建议单库不超过300-400个表
保持表身段苗条
表字段数少而精
√ IO高效 <br>√全表遍历 <br>√表修复快<br>√提高并发<br>√alter table快<br>
单表多少字段合适?
单表1G体积 500W行评估
顺序读1G文件需N秒<br>单行不超过200Byte<br>单表不超50个纯INT字段<br>单表不超20个CHAR(10)字段
单表字段数上限控制在20~50个
平衡范式不冗余
平衡是门艺术<br>严格遵循三大范式?<br>效率优先、提升性能<br>没有绝对的对与错<br>适当时牺牲范式、加入冗余<br>但会增加代码复杂度
拒绝3B
数据库并发像城市交通<br>非线性增长<br>
拒绝3B<br>大SQL (BIG SQL)<br>大事务 (BIG Transaction)<br>大批量 (BIG Batch)
二.字段类军规(6)
用好数值字段类型
三类数值类型:
TINYINT(1Byte)<br>SMALLINT(2B)<br>MEDIUMINT(3B)<br>INT(4B)、BIGINT(8B)
FLOAT(4B)、DOUBLE(8B)
DECIMAL(M,D)
BAD CASE:
INT(1) VS INT(11)
BIGINT AUTO_INCREMENT
DECIMAL(18,0)
将字符转化为数字
数字型VS字符串型索引<br>
更高效<br>查询更快<br>占用空间更小
实例:用无符号INT存储IP,而非CHAR(15)
INT UNSIGNED<br>INET_ATON()<br>INET_NTOA()
优先使用ENUM或SET
优先使用ENUM或SET<br>字符串<br>可能值已知且有限
存储
ENUM占用1字节,转为数值运算<br>SET视节点定,最多占用8字节<br>比较时需要加‘ 单引号(即使是数值)
实例
`sex` enum('F','M') COMMENT '性别'<br>`c1` enum('0','1','2','3') COMMENT '职介审核'
避免使用NULL字段
避免使用NULL字段
很难进行查询优化<br>NULL列加索引,需要额外空间<br>含NULL复合索引无效
实例
<strike>`a` char(32) DEFAULT NULL<br>`b` int(10) NOT NULL</strike><br>`c` int(10) NOT NULL DEFAULT 0
少用并拆分TEXT/BLOB
TEXT类型处理性能远低于VARCHAR<br>强制生成硬盘临时表<br>浪费更多空间<br>VARCHAR(65535)==>64K (注意UTF-8)
•尽量不用TEXT/BLOB数据类型
•若必须使用,则拆分到单独的表
实例:<br>
CREATE TABLE t1 (<br>id INT NOT NULL AUTO_INCREMENT,<br>data text NOT NULL,<br>PRIMARY KEY (id)<br>) ENGINE=InnoDB;
不在数据库里存图片
子主题<br>
三.索引类军规(5)
谨慎合理添加索引
谨慎合理添加索引
改善查询<br> 减慢更新<br> 索引不是越多越好
•能不加的索引尽量不加
综合评估数据密度和数据分布<br>最好不超过字段数20%
•结合核心SQL优先考虑覆盖索引
•实例
不要给“性别”列创建索引
字符字段必须建前缀索引
区分度<br>单字母区分度:26<br>4字母区分度:26*26*26*26=456,976<br>5字母区分度: 26*26*26*26*26=11,881,376<br>6字母区分度: 26*26*26*26*26*26=308,915,776
字符字段必须建前缀索引
`pinyin` varchar(100) DEFAULT NULL COMMENT '小区拼音', KEY `idx_pinyin` (`pinyin`(8)), ) ENGINE=InnoDB
不在索引列做运算
不在索引列进行数学运算或函数运算<br>无法使用索引<br>导致全表扫描<br>
实例
<strike>BAD:select * from table WHERE to_days(current_date) – to_days(date_col) <= 10</strike><br>GOOD: select * from table WHERE date_col >= DATE_SUB('2011-10-22',INTERVAL 10 DAY);
自增列或全局ID做INNODB主键
• 对主键建立聚簇索引<br>• 二级索引存储主键值<br>• 主键不应更新修改<br>• 按自增顺序插入值<br>• 忌用字符串做主键<br>• 聚簇索引分裂<br>• 推荐用独立于业务的AUTO_INCREMENT列或全局ID生成器做代理主键<br>• 若不指定主键,InnoDB会用唯一且非空值索引代替<br>
尽量不用外键
•线上OLTP系统(线下系统另论)
外键可节省开发量<br>有额外开销<br>逐行操作<br>可‘到达’其它表,意味着锁<br>高并发时容易死锁
•由程序保证约束
四.SQL类军规(15)
SQL语句尽可能简单<br>
大SQL VS 多个简单SQL
传统设计思想<br>BUT MySQL NOT<br>一条SQL叧能在一个CPU运算<br>5000+ QPS的高幵发中,1秒大SQL意味着?<br>可能一条大SQL就把整个数据库堵死
拒绝大SQL,拆解成多条简单SQL
<div>简单SQL缓存命中率更高</div><div>减少锁表时间,特别是MyISAM</div><div>用上多CPU</div>
保持事务(连接)短小
保持事务/DB连接短小精悍
事务/连接使用原则:即开即用,用完即关<br>不事务无关操作放到事务外面, 减少锁资源的占用<br>不破坏一致性前提下,使用多个短事务代替长事务
举例
发贴时的图片上传等待<br>大量的sleep连接
尽可能避免使用SP存储过程/TRIG触发器/FUNC函数
线上OLTP系统(线下库另论)
尽可能少用存储过程<br>尽可能少用触发器<br>减用使用MySQL函数对结果进行处理
由客户端程序负责
尽量不用 SELECT *
用SELECT * 时<br>
•更多消耗CPU、内存、IO、网络带宽<br>•先向数据库请求所有列,然后丢掉不需要列?
尽量不用SELECT * ,只取需要数据列
<br>•更安全的设计:减少表变化带来的影响<br>•为使用covering index提供可能性<br>•Select/JOIN减少硬盘临时表生成,特别是有TEXT/BLOB时
实例
【错误】SELECT * FROM tag WHERE id = 999184
【改正后】SELECT keyword FROM tag WHERE id = 999184
改写OR
改写OR为IN()
同一字段,将or改写为in()
•OR效率:O(n)<br>•IN 效率:O(Log n)<br>•当n很大时,OR会慢很多
注意控制IN的个数,建议n小于200
实例
【错误】Select * from opp WHERE phone=‘12347856' or phone=‘42242233'
【修改后】 Select * from opp WHERE phone in ('12347856' , '42242233')
改写OR为UNION<br>
不同字段,将or改为union
•减少对不同字段进行 "or" 查询<br>•Merge index往往很弱智<br>•如果有足够信心:set global optimizer_switch='index_merge=off';
实例
【错误】Select * from 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<br>使用不了索引<br>导致全表扫描
实例
MySQL> select * from post WHERE title like ‘北京%' ; <br>298 rows in set (0.01 sec) <br><br>MySQL> select * from post WHERE title like '%北京%' ; <br>572 rows in set (3.27 sec)<br>
减少COUNT(*)
COUNT(*)的几个例子
几个有趣的例子<br>
COUNT(COL) VS COUNT(*)<br>COUNT(*) VS COUNT(1)<br>COUNT(1) VS COUNT(0) VS COUNT(100)
子主题
示例
子主题
结论
COUNT(*)=count(1)<br>COUNT(0)=count(1)<br>COUNT(1)=count(100)<br>COUNT(*) !=count(col)<br>WHY?
MyISAM VS INNODB
不带 WHERE COUNT()<br>带 WHERE COUNT()
COUNT(*)的资源开销大,尽量<strike>不用</strike>少用
计数统计<br>
实时统计:用memcache,双向更新,凌晨跑基准<br>非实时统计:尽量用单独统计表,定期重算
LIMIT高效分页
传统分页
Select * from table limit 10000,10;
LIMIT原理
Limit 10000,10<br>偏移量越大,则越慢
反页方式
推荐分页
select * from table WHERE id>=23423 limit 11; <br>#10+1 (每页10条)<br>select * from table WHERE id>=23434 limit 11;<br>
分页方式二
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,10) USING (id) ;
分页方式四
程序取ID:select id from table limit 10000,10;<br>Select * from table WHERE id in (123,456…) ;<br>
•可能需按场景分析并重组索引<br>
实例
MySQL> select sql_no_cache * from post limit 10,10; 10 row in set (0.01 sec) <br>MySQL> select sql_no_cache * from post limit 20000,10; 10 row in set (0.13 sec) <br>MySQL> select sql_no_cache * from post limit 80000,10; 10 rows in set (0.58 sec) <br>MySQL> select sql_no_cache id from post limit 80000,10; 10 rows in set (0.02 sec) <br>MySQL> select sql_no_cache * from post WHERE id>=323423 limit 10; 10 rows in set (0.01 sec) <br>MySQL> select * from post WHERE id >= ( select sql_no_cache id from post limit 80000,1 ) limit 10 ; 10 rows in set (0.02 sec)<br>
用UNION ALL 而非 UNION
若无需对结果进行去重,则用UNION ALL<br>UNION有去重开销
实例
MySQL><br>SELECT * FROM detail20091128 UNION ALL <br>SELECT * FROM detail20110427 UNION ALL <br>SELECT * FROM detail20110426 UNION ALL <br>SELECT * FROM detail20110425 UNION ALL <br>SELECT * FROM detail20110424 UNION ALL <br>SELECT * FROM detail20110423;<br>
分解联接保证高并发
高并发DB不建议进行两个表以上的JOIN
适当分解联接保证高幵发
<br>可缓存大量早期数据<br>使用了多个MyISAM表<br>对大表的小ID IN()<br>联接引用同一个表多次
实例:
MySQL> Select * from tag JOIN tag_post on tag_post.tag_id=tag.id JOIN post on tag_post.post_id=post.id WHERE tag.tag=‘二手玩具’;<br><br> MySQL> Select * from tag WHERE tag=‘二手玩具’; <br>MySQL> Select * from tag_post WHERE tag_id=1321; <br>MySQL> Select * from post WHERE post.id in (123,456,314,141)<br>
GROUP BY 去除排序
GROUP BY 实现<br>分组<br>自劢排序<br>•无需排序:Order by NULL<br>•特定排序:Group by DESC/ASC
实例
MySQL> select phone,count(*) from post group by phone limit 1 ; 1 row in set (2.19 sec)<br> MySQL> select phone,count(*) from post group by phone order by null limit 1; 1 row in set (2.02 sec)<br>
同数据类型的列值比较
原则
数字对数字,字符对字符
数值列不字符类型比较
同时转换为双精度<br>进行比对
字符列不数值类型比较
字符列整列转数值<br>不会使用索引查询
实例
字符列不数值类型比较<br> 字段:`remark` varchar(50) NOT NULL COMMENT '备注,默认为空', <br><br>MySQL>SELECT `id`, `gift_code` FROM gift WHERE `deal_id` = 640 AND remark=115127; <br>1 row in set (0.14 sec) <br><br>MySQL>SELECT `id`, `gift_code` FROM pool_gift WHERE `deal_id` = 640 AND remark='115127'; <br>1 row in set (0.005 sec)<br>
Load data 导数据
批量数据快导入
成批装载比单行装载更快,不需要每次刷新缓存<br> 无索引时装载比索引装载更快<br> Insert values ,values,values 减少索引刷新<br> Load data比insert快约20倍
尽量不用 INSERT ... SELECT
延迟<br>同步出错
打散大批量更新
大批量更新凌晨操作,避开高峰
凌晨丌限制<br>白天上限默认为100条/秒(特殊再议)
实例:
update post set tag=1 WHERE id in (1,2,3); <br>sleep 0.01; <br><br><br>update post set tag=1 WHERE id in (4,5,6); <br>sleep 0.01; ……<br>
Know Every SQL
MySQLsla<br>EXPLAIN<br>SHOW PROFILE<br>Show Slow Log<br>Show Processlist<br>SHOW QUERY_RESPONSE_TIME(Percona)<br>MySQLdumpslow<br>
五.约定类军规(5)
隔离线上线下
构建数据库的生态环境
•开发无线上库操作权限
开发不能上生产操作
原则:线上连线上,线下连线下<br>
实时数据用real库<br>模拟环境用sim库<br>测试用qa库<br>开发用dev库
案例:
<b>子主题</b>
禁止未经DBA确认的子查询
MySQL子查询<br>大部分情况优化较差<br>特别WHERE中使用IN id的子查询<br>一般可用JOIN改写
实例:
MySQL> select * from table1 where id in (select id from table2); <br>MySQL> insert into table1 (select * from table2); //可能导致复制异常<br>
永远不在程序端显式加锁
永远不在程序端对数据库显式加锁
•外部锁对数据库不可控<br>•高并发时是灾难<br>•极难调试和排查
并发扣款等一致性问题
<br>•采用事务<br>•相对值修改<br>•Commit前二次较验冲突
统一字符集为UTF8
字符集:
MySQL 4.1 以前叧有latin1<br>为多语言支持增加多字符集<br>也带来了N多问题<br>保持简单
统一字符集:UTF8
校对规则:utf8_general_ci
乱码:SET NAMES UTF8
统一命名规范
•库表等名称统一用小写<br>Linux VS Windows<br>MySQL库表大小写敏感<br>字段名的大小写丌敏感
索引命名默认为“idx_字段名”
库名用缩写,尽量在2~7个字母
DataSharing ==> ds
注意避免用保留字命名
举例: Select * from return; <br>Select * from `return`;<br>
保留字一览表
ADD<br>ALL<br>ALTER<br>GOTO<br>GRANT<br>GROUP<br>PURGE<br>RAID0<br>RANGE<br>ANALYZE<br>AND<br>AS<br>HAVING<br>HIGH_PRIORITY<br>HOUR_MICROSECOND<br>READ<br>READS<br>REAL<br>ASC<br>ASENSITIVE<br>BEFORE<br>HOUR_MINUTE<br>HOUR_SECOND<br>IF<br>REFERENCES<br>REGEXP<br>RELEASE<br>BETWEEN<br>BIGINT<br>BINARY<br>IGNORE<br>IN<br>INDEX<br>RENAME<br>REPEAT<br>REPLACE<br>BLOB<br>BOTH<br>BY<br>INFILE<br>INNER<br>INOUT<br>REQUIRE<br>RESTRICT<br>RETURN<br>CALL<br>CASCADE<br>CASE<br>INSENSITIVE<br>INSERT<br>INT<br>REVOKE<br>RIGHT<br>RLIKE<br>CHANGE<br>CHAR<br>CHARACTER<br>INT1<br>INT2<br>INT3<br>SCHEMA<br>SCHEMAS<br>SECOND_MICROSECOND<br>CHECK<br>COLLATE<br>COLUMN<br>INT4<br>INT8<br>INTEGER<br>SELECT<br>SENSITIVE<br>SEPARATOR<br>CONDITION<br>CONNECTION<br>CONSTRAINT<br>INTERVAL<br>INTO<br>IS<br>SET<br>SHOW<br>SMALLINT<br>CONTINUE<br>CONVERT<br>CREATE<br>ITERATE<br>JOIN<br>KEY<br>SPATIAL<br>SPECIFIC<br>SQL<br>CROSS<br>CURRENT_DATE<br>CURRENT_TIME<br>KEYS<br>KILL<br>LABEL<br>SQLEXCEPTION<br>SQLSTATE<br>SQLWARNING<br>CURRENT_TIMESTAMP<br>CURRENT_USER<br>CURSOR<br>LEADING<br>LEAVE<br>LEFT<br>SQL_BIG_RESULT<br>SQL_CALC_FOUND_ROWS<br>SQL_SMALL_RESULT<br>DATABASE<br>DATABASES<br>DAY_HOUR<br>LIKE<br>LIMIT<br>LINEAR<br>SSL<br>STARTING<br>STRAIGHT_JOIN<br>DAY_MICROSECOND<br>DAY_MINUTE<br>DAY_SECOND<br>LINES<br>LOAD<br>LOCALTIME<br>TABLE<br>TERMINATED<br>THEN<br>DEC<br>DECIMAL<br>DECLARE<br>LOCALTIMESTAMP<br>LOCK<br>LONG<br>TINYBLOB<br>TINYINT<br>TINYTEXT<br>DEFAULT<br>DELAYED<br>DELETE<br>LONGBLOB<br>LONGTEXT<br>LOOP<br>TO<br>TRAILING<br>TRIGGER<br>DESC<br>DESCRIBE<br>DETERMINISTIC<br>LOW_PRIORITY<br>MATCH<br>MEDIUMBLOB<br>TRUE<br>UNDO<br>UNION<br>DISTINCT<br>DISTINCTROW<br>DIV<br>MEDIUMINT<br>MEDIUMTEXT<br>MIDDLEINT<br>UNIQUE<br>UNLOCK<br>UNSIGNED<br>DOUBLE<br>DROP<br>DUAL<br>MINUTE_MICROSECOND<br>MINUTE_SECOND<br>MOD<br>UPDATE<br>USAGE<br>USE<br>EACH<br>ELSE<br>ELSEIF<br>MODIFIES<br>NATURAL<br>NOT<br>USING<br>UTC_DATE<br>UTC_TIME<br>ENCLOSED<br>ESCAPED<br>EXISTS<br>NO_WRITE_TO_BINLOG<br>NULL<br>NUMERIC<br>UTC_TIMESTAMP<br>VALUES<br>VARBINARY<br>EXIT<br>EXPLAIN<br>FALSE<br>ON<br>OPTIMIZE<br>OPTION<br>VARCHAR<br>VARCHARACTER<br>VARYING<br>FETCH<br>FLOAT<br>FLOAT4<br>OPTIONALLY<br>OR<br>ORDER<br>WHEN<br>WHERE<br>WHILE<br>FLOAT8<br>FOR<br>FORCE<br>OUT<br>OUTER<br>OUTFILE<br>WITH<br>WRITE<br>X509<br>FOREIGN<br>FROM<br>FULLTEXT<br>PRECISION<br>PRIMARY<br>PROCEDURE<br>XOR<br>YEAR_MONTH<br>ZEROFILL
0 条评论
下一页