前言
当单表记录数过大时,增删改查性能都会急剧下降,可能原因:没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 、I/O吞吐量小,形成了瓶颈效应、没有创建计算列导致查询不优化、内存不足、网络速度慢 、查询出的数据量过大(可以采用多次查询,其他的方法降低数据量)、锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷) 、sp_lock,sp_who,活动的用户查看,原因是读写竞争资源、返回了不必要的行和列 、查询语句不好,没有优化 ,可以参考以下步骤来优化。
单表优化
除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候postgresql单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:
字段
- 1、尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
- 2、VARCHAR的长度只分配真正需要的空间
- 3、使用枚举或整数代替字符串类型
- 4、尽量使用TIMESTAMP而非DATETIME
- 5、单表不要有太多字段,建议在20以内字段
- 6、避免使用NULL字段,很难查询优化且占用额外索引空间
- 7、用整型来存IP
索引
- 1、索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
- 2、应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
- 3、值分布很稀少的字段不适合建索引,例如”性别”这种只有两三个值的字段
- 4、字符字段只建前缀索引(前缀索引是一种能使索引更小,更快的有效办法,但另一方面也有其缺点:mysql无法使用其前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描。)
- 5、字符字段最好不要做主键
- 6、不用外键,由程序保证约束(假设一张表名为user_tb。那么这张表里有两个外键字段,指向两张表。那么,每次往user_tb表里插入数据,就必须往两个外键对应的表里查询是否有对应数据。如果交由程序控制,这种查询过程就可以控制在我们手里,可以省略一些不必要的查询过程。但是如果由数据库控制,则是必须要去这两张表里判断。)
- 7、尽量不用UNIQUE,由程序保证约束
- 8、使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引(建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。)
查询SQL
- 2、不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
- 3、sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
- 4、不用SELECT *
- 5、OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
- 6、不用函数和触发器,在应用程序实现
- 7、避免%xxx式查询(避免模糊查询)
- 8、少用JOIN
- 9、使用同类型进行比较,比如用'123'和'123'比,123和123比
- 10、尽量避免在WHERE子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
- 11、对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
- 12、列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大
从读主写分离
也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离
表分区
表分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码(表分区是将一个表的数据按照一定的规则⽔水平划分为不同的逻辑块,并分别进行物理存储,这个规则就叫做分区函数,可以有不同的分区规则)。
对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引
用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化。
当表中含有主键或唯一键时,则每个被用作分区函数的字段必须是表中唯一键和主键的全部或部分,否则就无法创建分区表,但是同时有主键和唯一键则必须时主键和唯一键都有;
表分区本质上也是一种特殊的库内分表、库内分表,仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻postgresql服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库来解决。
垂直拆分
垂直分库是根据数据库里面的数据表的相关性进行拆分。
说明:一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面。
解释:专库专用
优点:
1、拆分后业务清晰,拆分规则明确。
2、系统之间整合或扩展容易。
3、数据维护简单。
缺点:
1、部分业务表无法join,只能通过接口方式解决,提高了系统复杂度。
2、受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
3、事务处理复杂。
水平拆分
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。水平拆分是通过某种策略将数据分片来存储,分库内分表和分库两部分,每片数据会分散到不同的postgresql表或库,达到分布式的效果,能够支持非常大的数据量。
通常情况下,我们使用取模的方式来进行表的拆分;比如一张有400W的用户表users,为提高其查询效率我们把其分成4张表users1,users2,users3,users4或者多个库中database1,database2,database13……
通过用ID取模的方法把数据分散到四张表内Id%4+1 = [1,2,3,4],然后查询,更新,删除也是通过取模的方法来查询。
例:QQ的登录表。假设QQ的用户有100亿,如果只有一张表,每个用户登录的时候数据库都要从这100亿中查找,会很慢很慢。如果将这一张表分成100份,每张表有1亿条,就小了很多,比如qq0,qq1,qq1…qq99表或者华南QQdatabase1,华中QQdatabase2,华北QQdatabase13……
说明:垂直拆分后遇到单机瓶颈,可以使用水平拆分。相对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库或者表中。
解释:相对于垂直拆分,水平拆分不是将表的数据做分类,而是按照某个字段的某种规则来分散到多个库多个表之中,每个表中包含一部分数据。
简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中 的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,主要有分表,分库两种模式:
优点:
1. 不存在单库大数据,高并发的性能瓶颈。
2. 对应用透明,应用端改造较少。
3. 按照合理拆分规则拆分,join操作基本避免跨库。
4. 提高了系统的稳定性跟负载能力。
缺点:
1. 拆分规则难以抽象。
2. 分片事务一致性难以解决。
3. 数据多次扩展难度跟维护量极大。
4. 跨库join性能较差。
拆分方案:
范围、枚举、时间、取模、哈希、指定等
总结
优化sql语句,优化表结构和索引,不过对那些百万级千万级的数据库表,即便是优化过后,查询速度还是满足不了要求。这时候我们就可以通过分表降低单次查询数据量,从而提高查询速度,一般分表的方式有两种:水平拆分和垂直拆分,两者各有利弊,适用于不同的情况。