美文网首页
MySql最佳实践-札记

MySql最佳实践-札记

作者: zhglance | 来源:发表于2020-07-16 15:32 被阅读0次

一、表:

  • 表和字段名字一般采用"_" 而不是使用驼峰;

  • 指定表级别字符集为“CharsetEncoding = utf8mb4”、“Collation = utf8mb4_general_ci”。 ps:// 作为中国人,你不会不考虑使用中文字符吧,那么请使用utf-8吧

  • 使用MySQL主要应用于OLTP(on-line transaction processing)业务,那么请使用InnoDB引擎吧,InnoDB是MySql 5.5.5以后版本的默认引擎,支持事务,支持行级锁等。如果是OLAP (Online Analytical Processing)系统,你还考虑使用MySql是不是就不合时宜呀,hive、Hbase和Elastic Search它不香吗?

  • 表、字段定义时,需要添加表注释,字段注释。这点很重要,要不然交接项目的时候十分痛苦。好多项目没有字段注释的一个原因是,线上的数据库都是有DBA维护,开发添加字段或者建表的SQL语句的时候,为了偷懒而不写注释,DBA也不关心是否有注释(毕竟DBA不去接手业务项目),给项目维护增加了太多的麻烦。

  • 采用合适的分库分表策略,要考虑数据库的扩容性,分表可以采取水平分表和垂直分表两种方式;

  • 禁止在数据库中以明文形式保存手机号,身份证号等敏感信息,如果非要保存,可以使用AES等对称加密算法(密码位数要大于128 bits)避免脱库之后的数据风险;

二、主键和索引:

  • 每张表必须要有主键,否则在MySql主从同步延迟增加,update的时候性能大大降低(曾经年少踩坑,惨痛教训);

  • 尽量采用自增id主键(避免使用uuid,插入数据的时候回导致索引树重建,降低性能);

  • 尽量不使用字符串作为主键,字符串的性能不如自增id主键;

  • 单表的索引个数不宜过多,一般3个左右,通常不超过5个,超过的话,还是想想自己的表是不是需要垂直拆分了;

  • 联合索引中的字段个数不宜过多,一般3个左右,通常不超过5个超过的话,还是想想自己的表是不是需要垂直拆分了;

  • 理解主键索引和非主键索引,主键索引的B+数叶子节点保存的是整行数据,非主键索引的B+数叶子节点保存的是主键ID,通过非主键索引查询的时候,如果select的字段不是全部在改非主键索引字段中,那么需要反查主键索引得到对应的整行数据。因此要学会使用覆盖索引,这样的话,使用非主键索引,就不需要反查主键索引了;

  • 尽量避免使用UUID,这些数据的插入会导致主键索引的重构,十分影响插入性能;

  • 禁止MD5/SHA等的hash计算值作为主键,一方面这些数据的插入会导致主键索引的重构,十分影响插入性能;另一方面一旦遇到Hash冲突,就会导致数据无法插入,导致系统bug; // 工作经验值,竟然有技术提出使用Hash作为主键,当时我也是很无语的,我就直接反击了;

  • update/delete尽量使用主键进行操作,如果不使用主键进行操作,where的条件字段要添加索引,否则每次update/delete是表锁,严重影响性能;

  • 针对order by、group by、distinct的字段,要创建索引,利用索引的先天顺序性,避免重新排序;

  • 创建索引的时候要将区分度大的字段放到前面,这样的话可以降低索引树的高度,增加索引树的宽度,这样查询的时候,减少B+索引树非叶子节点的IO次数,提高查询效率;

  • 避免创建冗余,重复和不需要的索引,索引个数的增加会给索引的维护增加很多不必要的性能损耗;

  • 索引字段的默认值禁止设置为default null,因为null值不纳入索引计算,如果查询is null 将进行全表扫描,严重影响查询效率; 通常字符串默认值为"", 数字默认为0,特殊数字类型设置为-1;

  • 尽可能使用唯一索引,这样提高查询效率;唯一索引(包括联合唯一索引)中的字段如果为null,可以在mysql中保存多条索引值相同的记录。即唯一性索引是允许多个 null 值的存在的。假如A字段为唯一索引,那么下面语句两次都会执行成功,不会抛出‘Duplicate key’ 的异常。
    insert into student (A) VALUES (NULL); // 插入成功
    insert into student (A) VALUES (NULL); // 插入成功

  • 如果逻辑不是整型自增类型,尽可能添加自增ID主键;

  • 禁止使用外键;// 这个好多企业基本都有规范了;

  • 在MySQL中,如果varchar字段特别长,MySQL仅对varchar的前n个字符做索引处理( MyISAM:是1000 个字节;InnoDB引擎是前 767 字节)。

三、字段:

  • create table所有的列都最好采用not null+ default value,避免数据库字段的值出现null;

  • 禁止使用unsigned int/unsigned bigint,这些类型映射到java bean时,有越界的风险;

  • 金额数字推荐使用bigint类型,单位是“分”;

  • 严禁将tinyint/unsigned tinyint 映射到java的short/byte,否则不但省不了内存,反而会带来很多麻烦,一定要使用Integer;// 具体原因待分析

  • alter table add column的时候,禁止新增字段有not null限制,如果线上运行的旧代码,还没有对改字段进行支持,特别是insert,那么会导致线上执行sql报错。

varchar的长度问题

MySql 5.0 之前的版本: n指的是n个字节,如果存放utf-8格式只能保存 (n / 3)个汉字,即如果varchar(20) 那么只能保存6个汉字;
MySql 5.0 之后的版本: n指的是n个字符,如果存放utf-8格式,那么无论是数字,字母还是汉字,都可存放n个,即如果varchar(20) 那么可以保存20个汉字(自己亲测可以);
gbk类型:varchar(n)中每个字符最多占2个字节,最大长度不能超过(65535 / 2);
utf-8类型:varchar(n)中每个字符最多占3个字节,最大长度不能超过(65535 / 3);

关于n的大小,看到有人建议设置为8的倍数或者(2^k)-1大小,// 这些理论依据待补充。

  • 针对访问频率低的字段,可以将冷字段放到单独的表中,实现垂直拆分,避免冷热数据混在一起的情况,提高效率;

  • 按日期时间分表需符合YYYY[MM][DD][HH]格式。

  • 尽量避免使用TEXT、BLOB类型,如果非用不可,则要将TEXT、BLOB类型字段拆到单独表中,即垂直拆分表;

  • 针对非负整数,建议使用UNSIGNED类型存储,这样存储的范围更大。

  • 使用UNSIGNED INT 存储IPV4(可以通过inet_ntoa和inet_aton函数进行转化),DECIMAL或者两个bigINT来存储IPV6(暂无处理函数)。

  • varchar是不区分大小写的,针对大小写敏感的变长字符串,可以使用varbinary二进制字符类型存储;

  • 尽量在表中添加create_time和modify_time 字段,modifyTime建议设置为:DEFAULT NOW() ON UPDATE CURRENT_TIMESTAMP,一般update_time要有索引;

备注:timestamp 类型的default CURRENT_TIMESTAMP

  • 禁止在MySql中存文件、照片等。一般讲文件保存到文件系统中,mysql中仅保存文件的访问路径;

datetime 和 timestamp 的区别:

类型 占据字节 表示形式 表示范围 说明
datetime 8 字节 yyyy-mm-dd hh:mm:ss '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999' 1.datetime以 8 个字节储存,不会进行时区的检索;
2.对于datetime来说,存什么拿到的就是什么;
3.如果存进去的是NULL, datetime会储存为NULL。
timestamp 4 字节 yyyy-mm-dd hh:mm:ss '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999' 1.timestamp 只占 4 个字节,而且是以utc的格式储存, 它会自动检索当前时区并进行转换。
2.时间范围只到2039年,需要特别注意业务场景;
3.对于timestamp来说,如果储存时的时区和检索时的时区不一样,那么拿出来的数据也不一样。
4.如果存进去的是NULL,timestamp会自动储存当前时间

四、SQL书写:

  • 禁止使用select * , 必须用具体的列名来代替,使用的话,必走索引反查,降低查询性能;

  • 禁止在where条件中使用函数,否则会导致索引失效;

  • 禁止使用子查询,原因?

  • update/delete只允许但表操作,不允许进行多表链接,不允许使用子查询;

  • 尽量避免使用delete语句,通常使用update set is_deleted = 1实现逻辑删除;

  • insert语句要用具体的列名;

  • 尽量避免使用or,可以使用用union或者union all进行合并。

  • 尽量避免在sql中进行计算。

  • 禁止使用 like ’%keyword%‘ 前缀模糊查询,会导致不走索引,全表扫描,这种情况可以使用全文索引,一般使用Elastic Search;

  • update/delete尽量根据主键进行操作

  • 尽量减少count()的使用,

count(*) : 所有的列,相当于数据行数,在统计结果的时候,字段值为null的也计入统计;
count(1): 所有的列,相当于数据行数,在统计结果的时候,字段值为null的也计入统计;
count(列名):只包括列名那一列,字段值为null时,不统计.
从上面原理可以理解:count(1)和count(*)基本无差别。
count性能由高到低:count(主键字段) > count(1) > count(非主键字段) > count(*)

  • group by的时候,没有排序,可以加上order by null

  • sql中禁止使用类型隐式转换,join中是用的字段类型一定要一样,理解inner join、left join、right join和outer join的区别

  • 尽量不要在sql语句中指定索引类型,join的字段要有索引,使用mysql内部的优化策略。

  • 尽量避免使用事务,如果使用事务,事务要简单,执行时间短,避免大事务;

  • 尽量避免使用join或者多表联查查询,特别是大表更应该禁止;

  • 尽量避免not in、<>、!=等负向查询,否则会导致索引失效;

  • 理解索引的最左匹配原则

  • limit实现分页,limit过大会影响效率,较大时可以配合子查询提高效率。

  • 尽量使用union all替代union,union进行了去重和排序,union all的不进行排序和去重。

  • 减少数据库访问次数,一次访问就会占用一次访问资源,比如insert可以插入多条数据,upsert语句避免了先查询后更新或者插入的问题(根据主键);

  • IN条件里面的数据数量不能太多,太多可以通过联查代替;

  • 使⽤sql预编译,通过绑定变量实现一次解析,多次使用,同时降低SQL注入的风险;

  • 通常禁止使用触发器、函数、视图、存储过程等;

  • 避免一次更新太多数据,建议不超过100行;

  • 禁止在线上数据库中执行时间长的sql;

  • 禁止使⽤order by rand();

  • 禁⽌单条SQL更新多个表的数据。

  • 尽量避免在区分度十分低的列上建立索引,如“性别”只有男、女两种值,"婚姻状况"只有未婚、已婚和离异等三种值。Oracle的位图索引适合在这种场景。

  • 数据库连接池的选择优先级:tomcat-jdbc > druid > dbcp2 > dbcp1.4 > c3p0

五、Explain执行计划

<未完待续>

相关文章

网友评论

      本文标题:MySql最佳实践-札记

      本文链接:https://www.haomeiwen.com/subject/qlwphktx.html