前段时间做了一次数据库主键uuid改为自增int降低插入数据iops的小小实践,当然影响插入iops的不仅仅是主键,如果其他索引也比较多,iops也可能不会有明显的降低,这跟索引的存储有关,文章的后面仔细探讨
一 背景
1、说明
数据库说明:
4核8G,iops最大5000,网络带宽富余,cpu和内存都是健康状态。存储引擎innodb
现有数据量和表结构说明:
亿级别,分多个表,每个表的数据量在百万级别。主键为varchar类型uuid,其他字段的索引2个。具体的数据结构因为涉及公司信息就不列出来了。
业务需求:
业务需要将百万级的数据尽可能快的插入到数据库中,并且数据是分在不同的分表中,单条单条插入。
性能瓶颈:
数据库插入速度受限,这里我们在应用服务器做了限流,这次优化主要是想提高数据插入速度
2、现状
插入速度恒定(假设每秒插入500)的情况下,iops越来越高。这里之所以限制每秒插入速度就是为了保证数据的稳定性。
二 存储原理探究
针对上述问题,我们在以下几个方面进行探讨
2.1 Innodb索引结构
关于索引网上的文章很多,这里不再赘述。
Innodb中主键索引的叶子节点的数据区域存储的是数据记录,辅助索引叶子节点不存储数据,存储的是主键值,当然不管是主键索引还是辅助索引在最终存储到磁盘上的时候都是存储到页上,一个完整的索引树也必然存储在N多的数据页上。
主键索引:
辅助索引:
2.2 UUID 主键有什么问题
目前存储引擎使用InnoDB, 将表中的数据存储在 B+ 树中,在数据库中我们称之为聚集索引。聚集索引自动将数据行按主键顺序存储。由于UUID的无序性,存储数据时会发生随机io
下面分析一个InnoDB作为数据引擎,主键为UUID的数据插入过程:
当你插入一行随机主键值的数据,InnoDB 需要找到这行应该属于哪一页,如果页没在缓冲池中则将其加载进缓冲池,插入数据行,最后将脏页刷回磁盘。纯随机值加上大表使得 B 树上的每个叶子节点都有机会插入行,而没有热点数据页。数据行不按照主键顺序(主键顺序指主键顺序的末端)插入会导致页的分裂,进一步导致页的填充因子降低。在缓冲池中,有新数据插入的页称为脏页。而缓冲池中的页在被刷回磁盘前再次有新数据需要写入的概率很低。所以大部分时间中,每次插入操作会导致两次 IO 过程——一次读取和一次写入。所以首先 UUID 会对 IO 操作的造成一定影响。
当然上面的过程只是描述了主键索引的构建保存,其他索引如果是无序的同样存在随机io的问题。所以索引越多也会降低插入数据的速度
2.3 自增整型主键的优势
字段长度较uuid小很多,目前考虑可以使用bigint或者int类型,而且辅助索引叶子节点存储的主键数据变少,可以减少部分存储空间。
在写的方面,因为是自增的,所以主键是趋势自增的,通俗的讲也就是说新增的数据永远在后面,会产生顺序io,这点对于性能有很大的提升
2.4 可能的潜在问题
高并发的情况下,竞争自增锁会降低数据库的吞吐能力(todo)
后期如果考虑数据迁移,或者重新分表,自增主键可能会有冲突,可以考虑使用有序uuid作为主键或者使用自增主键的同事增加一个uuid作为业务上的唯一标识
2.5 切换方法
新增主键为整型的新表,上线后线上数据双写,同时在新表和历史表保存
将历史数据逐渐同步到新表,同步完成后将所有业务切换到新表,保证服务的不间断运行和稳定性
三 插入性能测试
测试的数据库限制了带宽,其他索引两个
插入速度受网络带宽影响每秒470左右两种主键对比
1、已有数据量为0的时候插入200万数据
主键varchar(23:11-00:31,80min)iops:100-400
主键int (15:23-16:37,74min)iops:25-100
2、已有数据量单表600万,数据库性能对比,每秒插入450左右两种主键对比
(1)主键uuid类型
数据插入速度
iops
(2)主键int
插入速度
iops
4、结论
在其他索引不多的情况下,通过上述论证varchar主键改为整型可行,并且能有效降低iops使用率