MYSQL 性能优化

101 阅读7分钟

5

单表优化

除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量

  1. 字段设计层面的优化方案,总而言之就是能用整数表示就别用字符串
    • 单表不要有太多字段,建议在20以内

    • 避免使用NULL字段,很难查询优化且占用额外索引空间

    • 用整型来存IP

    • 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED,避免不必要空间的浪费

    • VARCHAR的长度只分配真正需要的空间,虽然是可变长的,但是规范下长度

    • 使用枚举或整数代替字符串类型,枚举和整数占用空间少

    • 尽量使用TIMESTAMP而非DATETIME,时间戳字符串长度会比DATETIME小点儿

  1. 索引设计层面的优化方案,总而言之就是不要胡乱创建索引,少用外键和唯一约束,因为也会有索引
    • 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描索引的维护和存储也耗费性能,所以别乱建索引
    • 应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
    • 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段,索引底层实现是B+树,杀鸡不能用牛刀
    • 字符字段只建前缀索引
    • 字符字段最好不要做主键
    • 不用外键,由程序保证约束
    • 尽量不用UNIQUE,由程序保证约束
    • 使用多列索引时注意顺序和查询条件保持一致,同时删除不必要的单列索引
  1. 查询SQL语句层面的优化方案,总而言之就是精细化查找,避免用不上索引
    • 不做列运算:任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边

    • sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库

    • 不用SELECT *

    • OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内

    • 不用函数和触发器,在应用程序实现

    • 避免%xxx式查询

    • 少用JOIN

    • 使用同类型进行比较,比如用’123’和’123’比,123和123比

    • 尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描

    • 对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5

    • 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大

读写分离

经典的数据库拆分⽅案,主库负责写,从库负责读。在我的另一个关于Redis的blog里有介绍到。

垂直拆分

垂直分库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。垂直分表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分【参照数据库第三范式】,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联

垂直拆分的优点是:

  • 可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少)
  • 可以达到最大化利用Cache的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起
  • 数据维护简单

垂直拆分的缺点是:

  • 主键出现冗余,需要管理冗余列
  • 会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力
  • 依然存在单表数据量过大的问题(需要水平拆分)
  • 事务处理复杂,因为锁是行级别的

其实综合来看垂直拆分的场景非常少,基本上在设计初期就不会在一张表上设计过多字段

水平拆分

保持数据表结构不变,通过某种策略存储数据分⽚。这样每⼀⽚数据分散到不同的表或者库中【分库分表】,达到了分布式的⽬的。 ⽔平拆分可以⽀撑⾮常⼤的数据量,这个才是我们一定会面临的随着业务不断增大而产生的问题

  • 库内分表仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻MySQL服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库分表来解决

所以说分库分表是我们最常用的解决方案

  • 水平拆分的优点是:不存在单库大数据和高并发的性能瓶颈;应用端改造较少;提高了系统的稳定性和负载能力
  • 水平拆分的缺点是:分片事务一致性难以解决;跨节点Join性能差,逻辑复杂;数据多次扩展难度跟维护量极大

水平拆分最大的问题就是如何保证分布式的主键id唯一性!因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要⼀个全局唯⼀的 id来⽀持。⽣成全局 id 有下⾯这⼏种⽅式:

  • UUID:不适合作为主键,因为太⻓了,并且⽆序不可读,查询效率低。⽐较适合⽤于⽣成唯⼀的名字的标示⽐如⽂件的名字。
  • 数据库⾃增 id : 两台数据库分别设置不同步⻓,⽣成不重复ID的策略来实现⾼可⽤。这种⽅式⽣成的 id 有序,但是需要独⽴部署数据库实例,成本⾼,还会有性能瓶颈。
  • 利⽤ redis ⽣成 id : 性能⽐较好,灵活⽅便,不依赖于数据库。但是,引⼊了新的组件造成系统更加复杂,可⽤性降低,编码更加复杂,增加了系统成本
  • Twitter的snowflake【雪花】算法 :
  • 美团的Leaf分布式ID⽣成系统 :Leaf 是美团开源的分布式ID⽣成器,能保证全局唯⼀性、趋势递增、单调递增、信息安全,⾥⾯也提到了⼏种分布式⽅案的对⽐,但也需要依赖关系数据库、
  • Zookeeper等中间件