关于标识列
标识列可能被拿来和其他值进行比较,也可能被用来查找其他标识列,同一个标识列也可能在另外的表中作为外键使用,因此标识列选择数据类型时,要和关联表中的对应列保持类型一致,为了追求更高地效率,所以不仅要考虑存储类型的选取,还需对Mysql如何执行计算、比较这个类型要有所了解。
当我们选定了一种类型,要确保在所有关联表中都使用相同的类型,类型间要精确匹配,包括unsigned这类属性。使用不同数据类型会导致性能问题,尤其是在比较操作时隐式类型转换也会导致较为晦涩而且具备隐藏性地错误。
在满足存储值的范围需求而且预留可数据增长空间的前提下,应该选择最小的数据类型,对于这个优化思路,有一些关注的点:
整数类型
整数类型通常时标识列最好的选择,处理速度非常快而且支持使用自增长处理auto_increment。
ENUM和SET
作为标识符的列在这里不建议使用enum和set类型,这两个类型的列更适合存储固定信息,如有限的状态列举。从这个思路出发考虑的话,用枚举列其实也可以被用来设计为作为一个表的主键进行查询,表中增加其他列为枚举列值进行说明,这样就得到一张术语表,但是这里有一个前提就是需要数据库维护者清晰了解这个枚举字段的使用和维护规则。
字符串类型
字符串类型比较消耗存储空间,而且处理速度比数字类型慢,特别是在使用MyISAM表里使用字符串作为标识列时,由于默认对字符串进行压缩索引,会导致查询更慢,最多有大约6倍的性能下降。
需要注意地是一些可以提供随机字符串的处理,如提供随机数的函数md5(),sha1(),uuid(),生成的新值会任意分布在很大的空间中,这样会分别导致insert和select操作速度下降,因为插入值会随机写到不同的索引位置,产生页分裂和磁盘随机访问以及让聚簇存储引擎产生聚簇索引碎片。对于查询效率的降低,是因为逻辑上相邻的行可能会分布在磁盘和内存的不同位置,随机值导致缓存的访问局部性原理失效,整个数据集都是一样的热数据,把任何部分的特定数据刷入缓存都是一样的,而且工作集比内存大很多,缓存必定会产生很多的刷新和不命中,最后导致对所有类型的查询语句效果都很差。
很多公司项目对于入库id的处理,一般会自行定制一个id生成器,目前采用雪花算法的居多,为了方便也可以采用Mysql自带的id生成器,在存储uuid值时,应该移除"-"符号,或者使用unhex()将uuid转换为16字节的数字并存于一个binary(16)列中,检索时通过hex()格式化为十六进制格式,uuid()生成的值与加密散列函数列如sha1()生成的值有不同的特征,其中uuid值分布不均匀,但是还是有一定顺序会有重复值的可能性,相比来说还是递增的整数较为好用。
关于自动生成的Schema
对于数据库工具的使用,本事是一件无可厚非的事情,使用工具可以提高将生产力翻倍提升,但是雷记建议对与工具的使用要加以斟酌。
就雷记个人经历处理过一些表结构的迁移,见过一些写的比较烂的数据库迁移脚本,也有一些自动生成schema的程序脚本,存储任何东西都会使用最大的varchar列,又或者对需要在关联时比较的列使用不同的数据类型,这种处理的方式对于数据库性能的消耗是灾难性的。
另外就雷记了解过的一些代码项目,使用了一些orm(对象关系映射)框架,这种设计对与开发者很有吸引力,凭借吹嘘可以使用面向对象的方式工作,不用考虑数据的如何存储,这种应用往往对开发者隐藏复杂性,往往使用的最后结果是难扩展,性能低下,以这些为代价换来开发人员的效率提高,而且当发现性能瓶颈时往往已经很晚。
关于特殊类型数据
另外还有一些类型的数据并不直接与内置类型一致,如之前文章有讲到的低于秒级精度的时间戳,在这里还有一个较为典型的就是ipv4地址,很多人使用varchar(15)来存储ip地址,但是实际上它们是32位无符号整数,并不是字符串,用xx.xx.xx.x的方式仅仅是为了方便阅读,所以用无符号整数存储ip地址才是最合适的,为在两种表示方法之间切换,Mysql提供:
INET_ATON():将ip地址转为无符号整数ip(4-8位)
INET_NTOA():将整型的ip转换为ip地址
结语
对于mysql数据类型的优化建议系列,大致就是这样,其中也只是抛砖引玉做的一番初探,希望各位看官也能从中得到些许收获。后续雷记还会继续更新一些从其他角度对mysql进行性能优化的帖子,还望各位看官多多捧场。
我是雷记,欢迎各位私信讨论各类问题!
