标签:
MySQL开发规范
持续借鉴、收集并整理一些开发规范和技巧,期望能更充分利用MySQL的特性,得到更好的性能。
规范是死的,人是活的。
现在定义的规范,是为以后推翻准备的。
提供给开发人员参考,方便写成更有效率的开发。
文档涉及的范围:需要基于MySQL做应用开发的人员。
暂无
目标三个:功能实现,可伸缩性,可用性。
关键点:平衡业务技术各个方面,做好取舍。
80%的性能优化来自架构设计的优化。
引擎建议使用InnoDB
根据目前我们业务的特点,建议使用MySQL5.1社区版和InnoDB plugin或MySQL5.5,后续MySQL5.6比较稳定后再行考量和评估。
开发大牛都擅长,这里不多提,仅标注一下。
如:md5() 或Order by Rand()或计算字段等操作不在数据库表上进行。
常见的有100库100表,1000库10表等。
建议单库不超过300-400个表。
总空间容量不超过100G。
IO高效;全表遍历;表修复快;提高并发;alter table快。
建议上限20~50个。
建议纯INT不超1000W,含CHAR不超500W。
单表1G体积 500W行评估:
顺序读1G文件需N秒
单行不超过200Byte
单表不超50个纯INT字段
单表不超20个CHAR(10)字段
大SQL (BIG SQL)
大事务 (BIG Transaction)
大批量 (BIG Batch)
无外键,少多表join查询。
便于分布式设计,允许适度冗余,为了容量扩展允许适度开销。
基于业务自由优化,基于i/o 或查询设计,无须遵循范式结构设计。
a) 原有展现程序涉及多个表的查询,希望精简查询程序。
b) 数据表拆分往往基于主键,而原有数据表往往存在非基于主键的关键查询,无法在分表结构中完成。
c) 存在较多数据统计需求(count, sum等),效率低下。
如:
消息表message,存在字段 from_uid,to_uid,msg,send_time 四个字段,而展示程序需要显示发送者姓名和性别。
通常在message表中增加冗余字段from_username和from_user_sex即可。
如:
用户分表,将用户库分成若干数据表。基于用户名的查询和基于uid的查询都是高并发请求。用户分表基于uid分成数据表,同时基于用户名做对应冗余表。
如果允许多方式登陆,可以有如下设计方法:
uid,passwd,用户信息等等,主数据表,基于uid分表
ukey,ukeytype,uid基于ukey分表,便于用户登陆的查询。分解成如下两个SQL:
select uid from ulist_key_13 where ukey=‘$username‘ and ukeytype=‘$login‘;
select * from ulist_uid_23 where uid=$uid and passwd=‘$passwd‘;
ukeytype定义用户的登陆依据,比如用户名,手机号,邮件地址,网站昵称等。 Ukey+ukeytype必须唯一。
此种方式需要登陆密码统一,对于第三方接入模式,可以通过引申额外字段完成。
如:
count(*)操作。
需要不精准结果,可以直接show table status like …获得。
需要精准结果,可以在缓存层增加key-value对,实时更新该key-value。同时异步更新到数据库中冗余字段,或冗余表中。
历史数据表对应于热点数据表。
将需求较少又不能丢弃的数据,仅在少数情况下被访问存入历史数据表。
直接使用sql语句where条件中使用like %fulltext%
直接全表扫描或全索引扫描,性能最差,无任何扩展,基本不可接受。
MyISAM全文索引,使用match()函数搜索。InnoDB从MySQL5.6.4开始支持全文索引,对中文支持不好,使用MATCH()…AGAINST。
并发不高,数据量不大,业务逻辑简单,可以考虑。
目前常用的有sphinx和lucene等。
适合并发高,数据量大,业务逻辑复杂的场景。
主要关注预热、增量更新及分片功能的实现。
Select * from table limit 10000,10;
Limit 10000,10
偏移量越大则越慢
Select * from table WHERE id>=23423 limit 11; #10+1 (每页10条)
select * from table WHERE id>=23434 limit 11;
可能需按场景分析并重组索引。
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;
Select * from table WHERE id in (123,456…) ;
MySQL> select sql_no_cache * from post limit 10,10;
10 row in set (0.01 sec)
MySQL> select sql_no_cache * from post limit 20000,10;
10 row in set (0.13 sec)
MySQL> select sql_no_cache * from post limit 80000,10;
10 rows in set (0.58 sec)
MySQL> select sql_no_cache id from post limit 80000,10;
10 rows in set (0.02 sec)
MySQL> select sql_no_cache * from post WHERE id>=323423 limit 10;
10 rows in set (0.01 sec)
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)
类似sina微博的首页,总共保留最新的500条微博,分10页。
分页样式类似如下:
如果使用的是InnoDB并且不需要特殊的聚簇。定义一个代理键(surrogate key)是个好的主意。意思就是这个主键并不是来自于你的应用程序的数据(与业务逻辑无关,而应用程序的数据如果有唯一的候选列可以做成唯一键),最简单的方法就是使用AUTO_INCREMENT列。这能保证数据插入保持着连续的顺序并且对于使用主键连接会获得更好的性能。最好避免使用随机的聚簇键。 对每张表,最重要的就是一定要有主键。
从性能的角度来说,使用UUID是个最不好的方法:它使聚簇索引的插入是随机的。这是最不好的场景了。并且对于数据的聚集也没有什么帮助。
UUID_SHORT()
UUID_SHORT()所占用的存储空间比UUID要小。(UUID_SHORT()可能要使用bigint占用8个字节,而UUID可能要使用字符串用char(32))。另外uuid_short()是顺序的,这个也解决了随机导致的问题,但是uuid_short()也有一些限制和bug。
没什么好说的,自增列简单实用。
仅注意一下锁(gap lock)问题即可。
建立类似如下表:
避免自增列引起的一些锁问题,统一管理,并发性更高。
更高的并发性,可以考虑从数据库中剥离,使用自己开发或第三方中间件,如:
https://github.com/twitter/snowflake
以MySQL5版本,int类型为例:
#建表
root@localhost(test2)14:46>create table test2 (a int(10) UNSIGNED);
Query OK, 0 rows affected (0.12 sec)
#插入数据
root@localhost(test2)14:56>insert test2 values (10);
Query OK, 1 row affected (0.00 sec)
#模拟更新溢出
root@localhost(test2)14:56>update test2 set a=a-11;
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 1
#查看warnings
root@localhost(test2)14:57>show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1264 | Out of range value for column ‘a‘ at row 1 |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)
#确定实际得到的值已经溢出
root@localhost(test2)14:57>select * from test2;
+------------+
| a |
+------------+
| 4294967295 |
+------------+
1 row in set (0.00 sec)
#清理数据
root@localhost(test2)14:59>delete from test2;
Query OK, 1 row affected (0.00 sec)
#模拟插入溢出
root@localhost(test2)14:59>insert test2 values (-1);
Query OK, 1 row affected, 1 warning (0.00 sec)
#查看warnings
root@localhost(test2)14:59>show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1264 | Out of range value for column ‘a‘ at row 1 |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)
#确定实际得到的值已经溢出
root@localhost(test2)14:59>select * from test2;
+------+
| a |
+------+
| 0 |
+------+
1 row in set (0.00 sec)
int占用4个字节,而int又分为无符号型和有符号性。对于无符号型的范围是0 到 4294967295;有符号型的范围是-2147483648 到 2147483647。
举一反三,其他类型都可能有类似问题,均需要考量。
可以通过sql_mode参数控制,但一般建议程序控制,比如:对表单项的值进行校验。
溢出和隐含转换导致的运行缓慢。
discuz论坛, uid是pk, mediumint类型, MyISAM表,总行数255万
#现象:更新比较慢,快7秒
MySQL> UPDATE cdb_members SET email=‘qin@zol.com.cn‘ WHERE uid=‘486851368‘;
Query OK, 0 rows affected (6.94 sec)
Rows matched: 0 Changed: 0 Warnings: 0
#通过观察语句发现有类型转换,因为uid为mediumint,而传过来的值是字符串‘2982891440‘
#查看该执行计划
MySQL> explain select * from cdb_members WHERE uid=‘2982891440‘;
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
1 row in set (0.00 sec)
#查看传过来的值是mediumint类型的2982891440的执行计划
MySQL> explain select * from cdb_members WHERE uid=2982891440;
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
1 row in set (0.00 sec)
#profiling的效果
这里的uid是mediumint, 这个条件的uid大小明显超过了范围。(参考 字段定义)
去掉uid=后面值的‘‘,速度就正常了。但是对于uid没有溢出的,加不加引号速度都一样。
在non-strict mode下,MySQL会自动帮你把字符串转换成整形,但是如果数值超出了范围,转换就会失败,所以MySQL就按照字符串来处理,因此不能使用索引。而从explain的结果上,并没有表现出这样的差别。
可以通过sql_mode参数控制,一般建议程序控制,比如:对表单项的值进行校验。
列类型 |
表达的范围 |
存储需求 |
TINYINT[(M)] [UNSIGNED] [ZEROFILL] |
-128到127 或 0到255 |
1个字节 |
SMALLINT[(M)] [UNSIGNED] [ZEROFILL] |
-32768到32767 或 0到65535 |
2个字节 |
INT[(M)] [UNSIGNED] [ZEROFILL] |
-2147483648到2147483647 或 0到4294967295 |
4个字节 |
BIGINT[(M)] [UNSIGNED] [ZEROFILL] |
-9223372036854775808到9223372036854775807 或 0到18446744073709551615 |
8个字节 |
DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL] |
整数最大位数(M)为65,小数位数最大(D)为30 |
变长 |
DATE |
YYYY-MM-DD |
3个字节 |
DATETIME |
YYYY-MM-DD HH:MM:SS(1001年到9999年的范围) |
8个字节 |
TIMESTAMP |
YYYY-MM-DD HH:MM:SS(1970年到2037年的范围) |
4个字节 |
CHAR(M) |
0<M<=255(建议CHAR(1)外,超过此长度的用VARCHAR) |
M个字符(所占空间跟字符集等有关系) |
VARCHAR(M) |
0<M<65532/N |
M个字符(N大小由字符集,以及是否为中文还是字母数字等有关系) |
TEXT |
64K个字符 |
所占空间跟字符集等有关系 |
a) 字段定义为NOT NULL。
b) 表字符集选择UTF8,对恶魔字符集emoji用utf8mb4,如果MySQL版本不支持时,应用收到这种字符集的时候先转码成定义的串再传入数据库,例如传来\x0F,应用先替换成[i_smile]这样的特定字符串再存入数据库。
c) 存储年使用YEAR类型。存储日期使用DATE类型。 存储时间(精确到秒)使用TIMESTAMP类型或INT。使用时间字段作为查询条件,尤其是以时间段查询时,应使用INT保存。
d) 整形定义中不添加长度,比如使用INT,而不是INT[4]。
e) 使用VARBINARY存储变长字符串。
f)自增序列类型的字段只能使用INT或者BIGINT,且明确标识出为无符号型(UNSIGNED),除非确实会出现负数,仅当该字段数字取值会超过42亿,才使用BIGINT类型;
g) int(10)和int(1)没有什么区别,10和1仅是宽度而已,在zerofill等扩展属性的时候有用或者特殊的命令行交互工具
root@localhost(test)10:39>create table test(id int(10) zerofill,id2 int(1));
Query OK, 0 rows affected (0.13 sec)
root@localhost(test)10:39>insert into test values(1,1);
Query OK, 1 row affected (0.04 sec)
root@localhost(test)10:56>insert into test values(1000000000,1000000000);
Query OK, 1 row affected (0.05 sec)
root@localhost(test)10:56>select * from test;
+------------+------------+
| id | id2 |
+------------+------------+
| 0000000001 | 1 |
| 1000000000 | 1000000000 |
+------------+------------+
2 rows in set (0.01 sec)
h)少量枚举或状态定义类需求不建议使用ENUM、SET、VARCHAR类型,使用TINYINT来代替。需在COMMENT信息中标明被枚举的含义。例如:
`is_disable` int(11) DEFAULT ‘0‘ COMMENT ‘0:启用 1:禁用 2:异常‘;
i)存储精确浮点数使用DECIMAL替代FLOAT和DOUBLE。
j)使用UNSIGNED存储非负数值。
k)使用INT UNSIGNED存储IPV4。
l) 使用短数据类型,比如取值范围为0-80时,使用TINYINT UNSIGNED。
m) 尽可能不使用TEXT、BLOB类型,如果确实需要将过大字段拆分到其他表中。
n) VARCHAR(N),N表示的是字符数不是字节数,比如VARCHAR(255),可以最大可存储255个汉字,需要根据实际的宽度来选择N,VARCHAR(N),N>5000时,使用BLOB类型。如果N<256时会使用一个字节来存储长度,如果N>=256则使用两个字节来存储长度。
o) 不在数据库中使用VARBINARY、BLOB存储图片、文件等。
p) 禁止在数据库中存储明文密码
q) 数据库中存放IP时,按功能确定字段类型。仅作展示功能的使用CHAR,作为查询功能的应使用INT类型存放。参见“将字符转化为数字”
r) 严格禁止在库名、表名中使用大写字母。
更高效,查询更快,占用空间更小
举例:用无符号INT存储IP,而非CHAR(15)
INT UNSIGNED
INET_ATON()
INET_NTOA()
root@localhost(test) 16:21>SELECT INET_ATON(‘192.168.0.1‘);
+--------------------------+
| INET_ATON(‘192.168.0.1‘) |
+--------------------------+
| 3232235521 |
+--------------------------+
1 row in set (0.00 sec)
root@localhost(test) 16:21>SELECT INET_NTOA(‘3232235521‘);
+-------------------------+
| INET_NTOA(‘3232235521‘) |
+-------------------------+
| 192.168.0.1 |
+-------------------------+
1 row in set (0.01 sec)
更高效,查询更快,占用空间更小
举例:用无符号INT存储日期和时间
INT UNSIGNED
FROM_UNIXTIME()
UNIX_TIMESTAMP()
原因:
很难进行查询优化;
NULL列加索引,需要额外空间;
含NULL复合索引无效。
举例:
不使用:`a` char(32) DEFAULT NULL
不使用:`b` int(10) NOT NULL
使用:`c` int(10) NOT NULL DEFAULT 0
如: ((a AND b) AND c OR (((a AND b) AND (c AND d))))
修改成 (a AND b AND c) OR (a AND b AND c AND d)
如: (a<b AND b=c) AND a=5
修改成 b>5 AND b=c AND a=5
如: (B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
修改成 B=5 OR B=6
如:1=1,2>1,1<2等 直接从where子句中去掉。
WHERE条件中的非等值条件(IN、BETWEEN、<、<=、>、>=)会导致后面的条件使用不了索引,因为不能同时用到两个范围条件。
常数表指:空表或只有1行的表。与在一个PRIMARY KEY或UNIQUE索引的WHERE子句一起使用的表。如:
SELECT * FROM t WHERE primary_key=1;
SELECT * FROM t1,t2 WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
字典表指:小数量的行。如:自定义的自增字段表,而不使用MySQL的AUTO_INCREMENT。
如果有一个ORDER BY子句和不同的GROUP BY子句,或如果ORDER BY或GROUP BY包含联接队列中的第一个表之外的其它表的列,则创建一个临时表。
比如:where primary_key <> 1或者primary_key not in(…),这样跨表的数据肯定超过30%了。
where status=1,其中1值非常少,主要是0值,比如一个表的记录删除用了一个状态位,而删除的记录又比较少。
原因参见“避免使用NULL字段”。
如果%必须放在首字符位置,参见“全文检索设计”
比如:id,date字段放在前面,而status这样的字段放在后面,具体的可以通过执行计划来把握。
方便综合评估索引,缓解因为索引过多导致的增删改的一些性能问题。
如:where abs(列)>3或where 列*10>100
参见“表字段设计”
比如:列为int,如果where 列=’1’,则会出现转换。
union all不去重,而少了排序操作,速度相对比union要快,如果没有去重的需求,优先使用union all。
Select * from opp WHERE phone=‘12347856‘ or phone=‘42242233‘;
考虑用
Select * from opp WHERE phone in (‘12347856‘ , ‘42242233‘);
Select * from opp WHERE phone=‘010-88886666‘ or cellPhone=‘13800138000‘;
考虑用
Select * from opp WHERE phone=‘010-88886666‘
union all
Select * from opp WHERE cellPhone=‘13800138000‘;
select id,count(*) from table group by id having age>=30 order by null;
考虑用
select id,count(*) from table where age>=30 group by id order by null;
Text/blob大字段的用法参见“字段选择”
参考“分页设计”
INSERT ... ON DUPLICATE KEY UPDATE
REPLACE INTO、INSERT IGNORE 、INSERT INTO VALUES(),(),()
UPDATE … WHERE ID IN(10,20,50,…)
正确示例:update Table set uid=uid+1000,gid=gid+1000 where id <=2 ;
错误示例:update Table set uid=uid+1000 and gid=gid+1000 where id <=2 ;
此时“uid=uid+1000 and gid=gid+1000”将作为值赋给uid,并且无Warning!!!
下面的表增加一列url_crc32,然后对url_crc32建立索引,减少索引字段的长度,提高效率。
CREATE TABLE all_url(ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,url VARCHAR(255) NOT NULL DEFAULT 0, url_crc32 INT UNSIGNED NOT NULL DEFAULT 0,index idx_url(url_crc32));
根据架构设计可以得到些文档。
主要了解业务需求怎样?方便定义维护目标。
主要方便跟进架构情况,为沟通做好铺垫。
方便规划数据库。
特别对领导非常关心的应用,比如:新闻客户端。
表预计行数(比如:100万条以上),表预计大小(比如:100M以上),历史数据维护策略(分表策略,迁移策略,清理策略等);
方便考虑定期optimize优化减少碎片,减少相关索引提供更好的写性能。
方便适当优化语句,合理评估索引。
大字段(text,blob),值域小的字段(比如字段status字段0表示失败,值分布少,并且用该字段查询时以该值居多,1表示成功)。
方便dba整体评估索引,为SQL审核用(生成SQL审核报告)。
防雪崩、防刷
如主从关系,用户权限等等(可以提供在运维部署说明文档中)
标签:
原文地址:http://www.cnblogs.com/huangf714/p/5908042.html