当前位置: 首页 > 科技观察

为什么我建议大家需要定期重建数据量大但性能很关键的表

时间:2023-03-14 13:20:18 科技观察

一般是当业务要查询的数据量和要维护的并发量高于限制时采用一定配置的单实例MySQL,分库分库。表的方案已解决。当然,也有很多新的SQL分布式数据库解决方案。如果你使用MySQL,可以考虑TiDB(它实现了MySQL协议,兼容MySQL客户端和SQL语句)。如果你正在使用PgSQL,那么你可以考虑使用YugaByteDB(它实现了PgSQL协议并兼容PgSQL客户端和SQL语句)。他们目前有自己的云部署方案,你可以试试:TiDBCloud。尤加字节云。但是对于传统的分库分表项目,底层数据库还是基于MySQL、PgSQL等传统关系型数据库。一般在业务开始的时候,会考虑按照某个shardingkey来划分一些表,比如order表。我们估计用户直接想查看的订单记录是在最近一年内的。如果是一年前,提供其他入口进行查询。这个时候不是业务数据库,而是归档数据库,比如HBase。比如我们预估一年内最大用户订单数不会超过10亿,更新并发TPS(非查询QPS)不会超过10万/s。那么我们可以考虑将其分成64张表(最好的数量是2^n,因为2^n的余数=AND运算在2^n-1上,减少了shardingkey的运算量)。然后我们会定期归档一年前的数据,用类似deletefromtable的语句“彻底删除”(注意这里去掉引号)。这样可以确保业务表的数据级别始终保持不变。但是时间久了,你会发现一些普通的带shardkey(这里是userid)的查询会有点慢,有的会走错localindex。查询越来越慢的原因比如这条SQL:select*fromt_pay_recordWHERE((user_id='user_id1'ANDis_del=0))ORDERBYidDESCLIMIT201.2.3.4.5.6.7.8.9。该表的分区键是user_id。一方面,正如我在《为什么我建议对复杂但性能关键的表的所有查询添加强制索引》中所说,数据量可能会超出我们的预期,导致一些碎片表大于一定的限制,从而导致inMySQL对索引的随机采样越来越不准确,因为统计数据不是实时更新的,而是只有当更新的行数超过一定百分比时才会更新。而且统计不是全量统计,而是抽样统计。所以当表的数据量很大的时候,这个统计数据很难做到非常准确。依赖于表本身的自动刷新数据机制,参数很难调整(主要是参数STATS_SAMPLE_PAGES,我们一般不会改STATS_PERSISTENT,不接受存入内存,这样如果数据库重启,表会有要重新分析,所以减少Slowstartuptime,我们不会关闭STATS_AUTO_RECALC,这会导致优化器分析的越来越不准确),很难预测调整什么值最合适。而且,业务的增长和用户行为导致的数据倾斜也难以预测。通过AlterTable修改表的STATS_SAMPLE_PAGES时,与AnalyzeTable的效果是一样的,都会给表加一个读锁,阻塞表上的更新和事务。所以不能用在这种网上业务键表上。所以最好从一开始就估计大表的震级,但这很难。所以我们考虑对于数据量比较大的表,最好通过分库分表的方式提前控制好每张表的数据量,但是业务增长和产品需求在不断迭代,越来越复杂.很难保证不会出现复杂索引的大表。这种情况下,我们需要在适当增加STATS_SAMPLE_PAGES的前提下,针对部分用户触发的关键查询SQL,使用强制索引引导到正确的索引。但是,有时即使索引正确,查询还是有点慢。看这条SQL扫描的数据行数,发现并不多。+----+------------+------------+------------+-------+--------------------------------------------------------------------------------------+--------------+--------+------+--------+--------+--------------+|编号|选择类型|表|分区|类型|可能的键|钥匙|密钥长度|参考|行|过滤|额外|+----+------------+------------+------------+------+--------------------------------------------------------------------------------------+------------+--------+------+--------+--------+------------+|1|简单|t_pay_record|空|索引|idx_user_id,idx_user_status_pay,idx_user_id_trade_code_status_amount_create_time_is_del|idx_user_id|32|空|16|0.01|使用where|+----+------------+------------+------------+--------+------------------------------------------------------------------------------------+------------+--------+------+--------+----------+------------+1.2.3.4.5。像这样的OccasionalslowSQL可能还会有,而且随着时间的推移会越来越多,这和MySQLInnoDB中的delete机制有关。目前大部分业务表使用的是InnoDB引擎,它们都使用默认的行格式Dynamic,在这种行格式下,当我们插入一条数据时,它的结构大致如下:在记录头中,有一个删除标记:当发生更新导致记录长度发生变化时,例如,变长字段的实际数据变长这样,原始记录被标记为删除,并在最后创建更新的记录。删除记录时,也只是一个删除标记,标记记录的头部。对于这种可能出现的碎片,MySQLInnoDB也有预期和措施,即每页InnoDB引擎只会存储占用93%空间的数据,剩下的就是让长度变化的更新不会导致数据丢失运行到其他页面。但是相对来说,如果删除就相当于彻底浪费了存储空间。一般情况下,这不会造成太大的性能损失,因为删除一般是删除旧数据,而更新一般是关注最近的数据。比如一个订单更新的时候,一般都是更新最近的订单,很少有很久以前的订单基本没有更新,归档删除的一般都是很久以前的命令。但是,随着业务越来越复杂,归档逻辑也越来越复杂。例如,不同类型的订单时效性不同,一年前未结算的预购订单可能无法存档。随着时间的推移,您的数据可能会变成这样:结果,您需要扫描很少的几页数据。随着时间的推移,创建的分片会越来越多,扫描的页面也会越来越多,这样SQL的执行速度就会越来越快。来得慢一点。以上是表本身对数据存储的影响。对于二级索引,由于MVCC机制的存在,索引字段的频繁更新也会造成索引出现很多空洞。参考文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html。InnoDB多版本并发控制(MVCC)以不同于聚集索引的方式对待二级索引。聚簇索引中的记录就地更新,它们的隐藏系统列指向撤消日志条目,从中可以聚簇重建早期版本的记录。二级索引记录不包含隐藏的系统列,也不会就地更新。我们知道,MySQLInnoDB在索引的原始位置更新聚簇索引。对于二级索引,如果二级索引列有更新,则在原记录上标记为删除,记录在新的地方。这和以前一样,会造成大量的存储碎片。总结一下:MySQLInnoDB的DynamicrowformatrecordUpdate和Delete语句改变记录的长度实际上是在标记原始记录的删除标记。虽然MySQLInnoDB为此优化了预留空间,但随着时间的推移,随着归档和删除数据的增多,会产生大量的内存碎片,降低扫描效率。MVCC机制更新二级索引列是通过给原始记录打上删除标记,然后记录到新的地方,导致二级索引的扫描效率随着时间的推移变慢。解决方案——重建表对于这种情况,我们可以通过重建表来解决。重建表实际上是一箭双雕:第一,可以优化这种存储碎片,减少扫描行数;其次,它可以重新分析,使SQL优化器收集的数据更加准确。在MySQL5.6.17之前,我们需要借助外部工具pt-online-schema-change来帮助我们完成表的重构。pt-online-schema-change工具的原理其实就是在内部创建一个新表,并在原表上添加触发器同步更新到新创建的表,同时将数据复制到新创建的表中。完成后获取全局锁并将新建表名修改为原表名,然后删除原表。MySQL5.6.17之后,Optimizetable命令改为OnlineDDL。只有准备阶段和最终提交阶段需要获取锁。中间执行阶段不需要加锁,即在不阻塞业务的情况下更新DML。参考官网??文档:https://dev.mysql.com/doc/refman/5.6/en/optimize-table.html。Mysql5.6.17之前,OPTIMIZETABLE不使用在线DDL。因此,当OPTIMIZETABLE正在运行时,表上不允许并发DML(INSERT、UPDATE、DELETE),并且二级索引的创建效率不高。从MySQL5.6.17开始,OPTIMIZETABLE对常规表和分区InnoDB表使用在线DDL,这减少了并发DML操作的停机时间。OPTIMIZETABLE触发的表重建就地完成。独占表锁仅在操作的准备阶段和提交阶段短暂使用。在准备阶段,更新元数据并创建中间表。在提交阶段,提交表元数据更改。InnoDB表使用OptimizeTable命令需要注意的几点:1.对于大多数InnoDB表的OptimizeTable其实相当于重建表+Analyze命令(相当于语句ALTERTABLE...FORCE),只是不同从Analyze命令来看,OptimizeTable是在线DDL,机制优化,只在准备阶段和最终提交阶段获取表锁,大大减少了业务DML的阻塞时间,也就是说,这是可以考虑在线执行的优化语句(对于MySQL5.6.17在这之后)mysql>优化表foo;+------------+--------+--------+--------------------------------------------------------------+|表|操作|消息类型|Msg_text|+---------+---------+---------+----------------------------------------------------------------+|测试.foo|优化|注意|表不支持优化,改为重新创建+分析||测试.foo|优化|状态|好的|+------------+------------+---------+----------------------------------------------------------------+1.2.3.4.5.6.7.2。即便如此,还是要选择在业务低谷时执行OptimizeTable,因为和其他OnlineDDL执行一样,会创建并记录一个临时日志文件,记录DDL操作期间,如果所有数据插入,DML更新、删除都是在业务高峰期执行的,很可能导致日志过大,超过innodb_online_alter_log_max_size的限制:mysql>OPTIMIZETABLEfoo;+------------+------------+------------+------------------------------------------------------------------------------------------------------------------+|表|操作|消息类型|Msg_text|+------------+------------+--------+--------------------------------------------------------------------------------------------------------------------+|测试.foo|优化|注意|表不支持优化,改为重新创建+分析||测试.foo|优化|错误|创建索引“PRIMARY”需要超过“innodb_online_alter_log_max_size”字节的修改日志。请重试。||测试.foo|优化|状态|好的|+------------+------------+---------+----------------------------------------------------------------------------------------------------------------------+1.2.3.4.5.6.7.8.3。在这种情况下,如果我们已经处于业务的非高峰期,但仍然报这个错误,我们可以稍微增加innodb_online_alter_log_max_size的大小,但不要太大,建议每次增加128MB(默认为128MB)如果这个太大,可能会出现两个问题:(1)在最后的提交阶段,因为日志太大,提交时间太长,导致锁定时间太长。(2)由于业务压力,一直在不断写入这个临时文件,但是一直赶不上,导致业务高峰来的时候语句还在执行。4、建议在执行过程中,如果要评估对线上业务的影响,可以监控wait/synch/sxlock/innodb/dict_sys_lock和wait/synch/sxlock/innodb/dict_operation_lock这两个锁。两把锁相关的锁事件太多,线上有明显的慢SQL。createorkill其他时候选择执行optimizetable语句。从events_waits_history中选择thread_id、event_id、event_name、timer_waitwhereevent_nameLike"%dict%"orderbythread_id;SELECTevent_name,COUNT_STARFROMevents_waits_summary_global_by_event_namewhereevent_nameLike"%dict%"ORDERBYSTARDECOUNT