




下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
MySQL·性能优化·InnoDBbufferpoolflush策略漫背我们知道InnoDB使用bufferpool来缓存从磁盘到内存的数据页。bufferpool通常由数个内存块加上一组控制结构体对象组成。内存块的个数取决于bufferpoolinstance的个了支持bufferpool的动态调整大小。Bufferpool的每个内存块通过mmap的方式分配内存,因此你会发现,在实例启动时虚存很高,而物理内存很低。这些大片的内存块又按照16KB划分为多个frame,用于数据页。虽然大多数情况下bufferpool是以16KB来数据页,但有一种例外:使用压缩表时,需要在内存中同时压缩页和解压页,对于压缩页,使用Binarybuddyallocator算法来分配内存空间。例如我们读入一个8KB的压缩页,就从bufferpool中取一个16KBblock,取其中8KB,剩下的8KB放到空闲链表上;如果紧跟着另外一个4KB的压缩页读入内存,就可以从这8KB中4KB,同时将剩下的4KB放到空闲链表上。为了管理bufferpool,每个bufferpoolinstance使用如下几个链表来管理LRU链表包含所有读入内存的数据页Flush_list包含被修改过的脏页unzip_LRU包含所有解压页Freelist上存放当前空闲的block另外为了避免查询数据页时扫描LRU,还为每个bufferpoolinstance了一个pagehash,通过spaceidpageno可以直接找到对应的page。一般情况下,当我们需要读入一个Page时,首先根据spaceidpageno找到对应的bufferpoolinstance。然后查询pagehash,如果pagehash中没有,则表示需要从磁盘。在读盘前首先我们需要为即将读入内存的数据页分配一个空闲的block。当freelist上存在空闲的block时,可以直接从freelist上;如果没有,就需要从unzip_lru或者lru上page。这里需要遵循一定的原则(参考函数buf_LRU_scan_and_free_block,首先尝试从unzip_lru上解压页如果没有,再尝试从Lru链表上PAGEFLUSH,单独从Lru上刷掉一个脏页,然后再重试。Bufferpool中的page被修改后,不是立刻写入磁盘,而是由线程定时写入,和大多数数据库系统一样,脏页的写盘遵循日志先行WAL原则,因此在每个block上都记录了一个最近被修改时的Lsn,写数据页时需要确保当前写入日志文件的redo不低于这个Lsn。然而基于WAL原则的刷脏策略可能带来一个问题:当数据库的写入负载过高时,产生redolog的速度极快,redolog可能很快到达同步checkpoint点。这时候需要进行刷脏来推进Lsn。由于这种行为是由用户线程在检查到redolog空间不够时触发,大量用户线程将可能陷入到这段低效PageCleaner线MySQL5.6中,开启了一个独立的pagecleaner线程来进行刷lrulistlist。默认每隔一秒运行一次,5.6版本里提供了一大堆的参数来控制pagecleaner的行为,包括这里我们不一一介绍,总的来说,如果你发现redolog推进的非常快,为了避免用户线程陷入刷脏,可以通过调大innodb_io_capacity_max来解决,该参数限制了每秒刷新的脏页上限,调大该值可以增加Pagecleaner线程每秒的工作量。如果你发现你的系统中freelist不足,总是需要脏页来获取空闲的block时,可以适当调大innodb_lru_scan_depth。该参数表示从每个bufferpoolinstancelru上扫描的深度,调大该值有助于多释放些空闲页,避免用户线程去做singlepageflush。为了提升扩展性和刷脏效率,在5.7.4版本里引入了多个pagecleaner线程,从而达到并行刷脏的效果。目前Pagecleaner并未和bufferpool绑定,其模型为一个协调线程多个工作线程,协调线程本身也是工作线程。因此如果innodb_page_cleaners设置为4,那么就是一个协调线程,加3个工作线程,工作方式为生产者-消费者。工作队列长度为bufferpoolinstance的个数,使用一个全局slot数组表示。slot的状态设置为PAGE_CLEANER_STATE_REQUESTED,并设置目标page数及lsn_limit,然后唤醒工作线程(pc_request)工作线程被唤醒后,从slot数组中取一个未被占用的slot,修改其状态,表示已被调度,然后对该slot所对应的bufferpoolinstance进行操作。直到所有的slot都被消费完后,才进入下一轮。通过这种方式,多个pagecleaner线程实现了并发flushbufferpool,从而提升flushdirtypage/lru的效率。MySQL5.7InnoDBflush策略优在之前版本中,因为可能同时有多个线程操作bufferpoolpage(在刷脏时会释放bufferpoolmutex),每次刷完一个page后需要回溯到链表尾部,使得扫描bp链表的时间复杂度O(N*N)。5.6版本中针对Flushlist的扫描做了一定的修复,使用一个指针来记录当前正在flush的page,待flush操作完成后,再看一下这个指针有没有被别的线程修改掉,如果被修改了,就回因此在5.7版本中对这个问题进行了彻底的修复,使用多个名为hazardpointer的指针,在需要扫描LIST时,下一个即将扫描的目标page,根据不同的目的分为几类:flush_hp:用作批量刷FLUSHlru_hp:用作批量刷LRUlru_scan_itr:用于从LRU链表上一个可替换的page,总是从上一次扫描结束的位置开始,而不是LRU尾部single_scan_itr:bufferpool中没有空闲block时,用户线程会从FLUSHLIST上单独一个可替换的page或者flush一个脏页,总是从上一次扫描结束的位置开始,而不是LRU尾部。后两类的hp都是由用户线程在尝试获取空闲block时调用,只有在推进到某个buf_page_t::old被设置成truepage大约从Lru链表尾部起至总长度的八分之三位置的page)时,再将指针重置到Lru尾部。这些指针在初始化bufferpool时分配,每个bufferpoolinstance都拥有自己的hp指针。当某个线程对bufferpool中的page进行操作时,例如需要从LRU中移除Page时,如果当前的page被设置为hp,就要将hp更新为当前Page的前一个page。当完成当前page的flush操作后,直接使用hp中的page指针进行下一轮flush。社区优一如既往的,PerconaServer5.6版本中针对bufferpoolflush做了不少的优化,主优化刷LRU流程该函数由pagecleaner线程调用原生的逻辑:依次flush每个bufferpoolinstance,每次扫描的深度通过参数后的逻辑为:每次flush一个bufferpoolLRU时,只刷一个chunk,然后再下一个instance,刷完所有instnace后,再回到前面再刷一个chunk。简而言之,把集中的flush操作进行了分散,其目的是分散压力,避免对某个instance的集中操作,给予其他线程bufferpool的机会。允许设定刷LRU/FLUSHLIST的超时时间,防止flush操作时间过长导致别的线程(例如尝试做singlepageflush的用户线程)stall住;当到达超时时间时,pagecleaner线程退出flush。避免用户线程参与刷buffer当用户线程参与刷bufferpool时,由于线程数的不可控,将产生严重的竞争开销,例freelist不足时做singlepageflush,以及在redo空间不足时,做dirtyflush,都会严重影响性能。PerconaServer允许选择让pagecleaner线程来做这些工作,用户线程只需要等待即可。出于效率考虑,用户还可以设置pagecleaner线程的cpu另外在Pagecleaner线程经过优化后,可以知道系统当前处于同步刷新状态,可以去做更激烈的刷脏(furiousflush),用户线程参与到其中,可能只会起到反作用。允许设置pagecleaner线程,purge线程,io线程,master线程的CPU调度优先级,并优先获得InnoDBmutex。使用新的独立线程来刷bufferpool的LRU链表,将这部分工作负担从page线程剥离的强化线程,让用户线程少参与到刷脏/checkpoint这类耗时操作中。MySQL·社区动态·5.6.23InnoDB相关本节了MySQL5.6.23的几个和InnoDB相关的主要bugfix,简单阐述下问题及解决方案问题当执行FLUSHTABLE..FOREXPORT命令时,会暂停purge线程的操作。这一步通过设置一个标记purge_sys->state的值为PURGE_STATE_STOP来告诉purge线程该停下来歇歇了。然而如果Purge线程当前正在函数srv_do_purge中工作,该函数会执行一个while循环,退出条件是当前servershutdown,或者上次purgepage数为0,并没有检查purge线程的状态是否被设置为PURGE_STATE_STOP;很显然,如果当前的historylist非常长,那么可能需要等待purge完成后,才能退出循环,而在用户看来,就好像hang了很久一样。推长historylist很容易:开启一个打开readview的事务(例如RR级别下执行一个SELECT)不做提交,同时有并发的DML,跑一段时间historylist就上去了。解在函数srv_do_purge函数的while退出条件中加上purge线程状态判断,如果被设置问题在执行InnoDBcrashrecovery阶段,如果发现不合法的大字段,就会去调用函数ib_warn_row_too_big去打印一条warning,函数为push_warning_printf。然而这个函数的目的是给客户端返回一条warning,而这时候系统还在恢复阶段,并没有合法的thd对束。早期版本5.6及之前的版本,我们可以定义非常大的blob字段,但如果字段太长,对这些字段的修改,可能导致redologcheckpoint点被覆盖,因为计算redolog空间是否足够,并没有依赖即将插入的redo记录长度,而仅仅是保留一定的比例。因此在5.6.22版本中做了限制:如果blob的长度超过innodb_log_file_size*innodb_log_files_in_group的十分之一时,就会更新失败,给用户返回DB_TOO_BIG_RECORD的错误码。这个问题在5.7版本里被彻底解决:每写4个blob外部页,检查一次redolog空间是否足够,如果不够用,就推进checkpoint点。解不调用push_warning_printf。补问题当我们通过alter语句修改一个被外键约束的列名时,由于没有从数据词典cache中将包含老列名的cache项掉,导致重载外键约束时失败。举个简单的例子QueryOK,0rowsaffected(0.00QueryOK,0rowsaffected(0.00INDEXidx(a)) |Level|Code| root@sb112:37:48>showRecords: Duplicates:0Warnings:QueryOK,0rowsaffected,1warning(0.01root@sb112:37:41>ALTERTABLEt1CHANGEaidQueryOK,0rowsaffected(0.00 FOREIGNKEY(b)REFERENCESt1(a)ONDELETECASCADEONroot@sb112:37:26>CREATETABLEt2(aINT bINT,INDEXroot@sb112:37:13>CREATETABLEt1(aINTNOTNULL,bINTNOT(`b`)REFERENCES`t1`(`a`)ONDELETECASCADEONUPDATE(`b`)REFERENCES`t1`(`a`)ONDELETECASCADEONUPDATEconstraintfails(`sb1`.`t2`,CONSTRAINT`t2_ibfk_1`FOREIGNERROR1452(23000):Cannotaddorupdateachildrow:aforeignroot@sb112:52:08>INSERTINTOt2VALUES(56,1rowinset(0.00)ENGINE=InnoDBDEFAULTKEY`idx``b`int(11)NOT`id`int(11)DEFAULTCreateTable:CREATETABLE`t1`Table:***************************1.rowroot@sb112:47:39>showcreatetable1rowinset(0.00 |Error|1215|Cannotaddforeignkeyconstraint可以看到,尽管t1表的a列已经被renameid,但打印出来的信息也并没有更正。当被外键约束的列名被修改时,将对应的外键项从数据词典cache中,当其被随后重新加载时补问题如上文所提到的,在新版本InnoDB中,对blob字段的数据操作需要保证其不超过总的redologfile大小的十分之一,但是返回的错误码DB_TOO_BIG_RECORD及打印的信息太容易让人mayhelp.Incurrentrowformat,BLOBprefixof768bytesisTEXTorBLOBorusingROW_FORMAT=DYNAMICERROR42000:Rowsizetoolarge(>####).Changingsomecolumns解输出更合适、更直观的错误信息,如下usingusingisgreaterthan10%ofredologsize.IncreasetheredologERROR42000:ThesizeofBLOB/TEXTdatainsertedinone补问题FLUSHTABLE操作在某些情况下可能导致实例crash如如下执行序列mysql>FLUSHTABLEmysql>FLUSHTABLE QueryOK,0rowsaffected(0.00mysql>LOCKTABLESt1WRITE,t1ASt0READ,t1ASt2QueryOK,0rowsaffected(0.00mysql>CREATETABLEt1(CTEXTCHARACTERSETUJIS)当执行FLUSHTABLE时,在重载表cache时,InnoDB层会针对每个表设置其状(ha_innobase::store_lock)。如果执行FLUSH操作,并且加的是读锁时,就会调用函数row_quiesce_set_state将table->quiesce设置为QUIESCE_START。在上例中,表t1的两个表名表均加读锁,造成重复设置状态为QUIESCE_START,导致断言失败。Tips:在5.6版本中,虽然有明确的FLUSHTABLE..FOREXPORT命令来协助转储ibd文件。但实际上,简单的FLUSHTABLE操作默认就会产生一个tbname.cfg的配置文件,拷贝该文件和ibd,可以将数据转移到其他实例上。table->quiesce用于标识操作状态,例如,如果标识为QUIESCE_START,就会在函数ha_innobase::external_lock中调用row_quiesce_table_start来启动配置文件的生成。解问题线上实例错误日志中偶尔出现“UNABLETOPURGEARECORD”,从bug系统来看,很多用当changebuffer模块以如下序列来缓存索引操作时可能产生上述错误信息记录被标记删除随后插入相同记录--Purge线程需要物理删除二级索引记录,操作被buffer--当读入物理页时,总是需要进行ibufmerge。如果执行到IBUF_OP_DELETE这种类型changebuffer时,发现记录并没有被标记删除,就会导致错误日志报错为了搞清楚逻辑,我们简单的理一下相关代码pool的控制结构体中,有一个成员buf_pool->watch[BUF_POOL_WATCH_SIZE],BUF_POOL_WATCH_SIZE的值为purge线程个数,用于辅助Purge操作。假定内存中没有对应的Page,Purge线程会做如下几件事儿首先查询bufferpool,看看page是否已经读入内存;如果不在内存中,则将pageno等信息到watch数组中,并插入pagehash(buf_pool_watch_set)。(如果随后page被读入内存,也会删除watch标记)集索引记录没有deletemark并且其trxid比当前的purgeview还旧时,不可以做Purge操作)随后在插入IBUF_OP_DELETE类型的ibuf记录时,还会doublecheck下该page是否被设为sentinel(ibuf_insert_low,buf_pool_watch_occurred),如果未被设置,表明已经page已经读入内存,就可以直接去做purge,而无需缓存了。对于普通的操作类型,例如IBUF_OP_INSERTIBUF_OP_DELETE_MARK,同样也会doublecheckpage是否读入了内存。在函数ibuf_insert中会调用buf_page_hash_get进行检查,如果page被读入内存,则不缓存操作,如果请求的Page被设为sentinel,则从buf_page_hash_get返回NULL,因此随后判定需要缓存该类型的标记删除记录,写入Purge线程设置page对应的sentinel,完成检查,准备调用插入相同记录,写入Purge线程写入解如果记录所在的page被设置了一个sentinel,那么对该page的并发插入操作就不应该缓存changebuffer中,而是直接去尝试物理页。问题解问题在恢复后立刻执行一次slowshutdown(innodb_fast_shutdown=0)可能产生断言失败crash。原因是当完成crashrecovery后,对于需要回滚的事务,会起单独的线程来执行,这时候如果shutdown实例,会触发触发purge线程内部断言失败:ut_a(n_pages_purged==0||srv_fast_shutdown!=0);解等待trx_rollback_or_clean_all_recovered完成后,再进行slow补PgSQL·特性分析·ReplicationPostgreSQL9.4已于2014年底正式发布了(阿里云的RDS将支持PG9.4)。在这个版本,我们看到了像Jsonb,LogicalDecoding,ReplicationSlot等新功能。对于ReplicationSlot,文档上介绍的不多,乍一看让人比较难理解是做什么的。其实,ReplicationSlot的出现,主要是为最终在PG内核实现逻辑和双向铺路的(目前,逻辑和双向在内核中还缺少很多功能点,需要借助BDR插件,见PGwiki,引入ReplicationSlot的背后原因见这里)。不过,作为9.4版本的一个主要功能,它不但可以用于逻辑,还可用于物理(或者叫StreamingReplication)。针对物理的ReplicationSlot称为PhysicalReplicationSlot。由于大家目前主要用的还只是PG自带的物理方式,我们就重点分析一下PhysicalReplicationSlot。使用PhysicalReplicationSlot,可以达到两个效果 requestedWALsegment00000001000000010000002Dhasbeen通过ReplicationSlot记录的从库状态,PG会保证从库还没有apply的日志,不会从主库的 里面清除或archive掉。而且,replicationslot的状态信息是持久化保存的,即便当允许应用连接从库做只读查询时,ReplicationSlot可以与运行时参数 cancelingstatementdue with下面看看PhysicalReplicationSlot的用法和内核实现用下面是启用ReplicationSlot的步骤,很简单 ingReplication的主库从库。涉及的参数有,listen_addresses(='*'),hot_standby(=on),wal_level(=hot_standby),max_wal_senders(=1),尤其注意配置max_replication_slots大于等于1。这些参数在主库创建replication++my_rep_slot_1 |postgres=#SELECT*此时replicationslot还不处于active状态3)在从库配置recovery.conf如下,然后重启从库pression=1pression=1primary_conninfo='user=pg001host=10.x.x.xprimary_slot_name=standby_mode=观察主库replicationslot的状态变化+++++++++|1812|||| my_rep_slot_1xmin|catalog_xmin| |plugin|slot_type|datoid|database|activepostgres=#SELECT*FROM 与hot_standby_feedback配合使用。在将从库的postgresql.conf文件中hot_standby_feedback选项设为on,重启从库即可内核实replicationslot是由下面的patch加入内核中的(22:45-(22:45-Sat,1Feb2014RobertHaas(22:45-Sat,1Feb2014authorRobertHaas Slotshavesomeadvantagesovertechniques,asexplainedinhot_standby_feedback=on)pruningoftupleswhoseremovalwouldwrite-aheadlogsegmentsneededbyastandby,aswellasoneitheramasterorastandbytopreventprematureremovalReplicationslotsareacrash-safedatastructurewhichcan这个patch改的文件不少,分析这些代码,我们重点关注下面的问题A)ReplicationSlot是如何在内核中创建的通过分析创建ReplicationSlot时调用的函数ReplicationSlotCreate,可以看出,ReplicationSlot实质上是内存中的一些数据结构,加上持久化保存到pg_replslot/<slot 中的二进制状态文件。在PG启动的时候,预先在共享内存中分配好这些数据结构所用内存(即一个大小为max_replication_slots的数组)。这些数据结构在用户创建ReplicationSlot时开始被使用。一个ReplicationSlot被创建并使用后,其数据结构和状态文件会被WAL(Write-Ahead-Log)的发送者(wal_sender)进程更新。另外,如果单纯从ReplicationSlot的名字,我们很容易觉得ReplicationSlot会创建新ReplicationSlot还是使用了wal_sender原有连接(由于一个从库一个wal_sender连接,所以一个从库和主库之间也只有一个active的ReplicationSlot)。ReplicationSlot的状态是如何被更新的很容易发现,ReplicationSlot的状态的更新有两种情况第一种是在ProcessStandbyHSFeedbackMessage这个函数被更新。这个函数是在处理wal_sender所收到的从库发回的feedbackreplymessage时调用的。通过这个函数,我们可以看出,每个wal_sender进程的ReplicationSlot(就是用户创建的ReplicationSlot)保存在MyReplicationSlot这个全局变量中。在处理从库发回的reply时,reply中的xmin信息会被提取出来,存入slotdata.xmineffective_xmin域中,并通过函数ProcArraySetReplicationSlotXmin,最终更新到系统全局的这里要注意,如果我们有多个ReplicationSlot(分别对应各自从库),则在更新全局结构procArray->replication_slot_xmin时,会选取所有slot中最小的xmin值。第二种是在ProcessStandbyReplyMessage中。这个函数处理从库发送的restartlsn信(即从库apply的日志的编号),会直接将其更新到replicationslotrestartlsn域中,并保存到磁盘,用于主库判断是否要保留日志不被archive。ReplicationSlot如何和hot_standby_feedback配合,来避免从库的查询的这里,从库的查询指的是下面的情况:从库上有正在运行的查询,而且运行时间很长;这时主库上在做正常的vaccum,清除掉无用的记录版本。但主库的vaccum是不知道从库的查询存在的,所以在清除时,不考虑从库的正在运行的查询,只考虑主库里面的事务状态。其结果,vacuum可能会清除掉从库查询中涉及的,仍然在使用的记录版本。当这些vaccum操作,通过日志同步到从库,而恰好从库的查询仍然没有运行完,vaccum就要等待或cancel这个查询,以保证同步正常继续和查询不出现错误的结果。这样,每当用户在从库运行长查询,就容易出现我们上面提到到queryerror。如何避免这种呢?目前最好的解决方案是使用hot_standby_feedback+ReplicationSlot。其原理简单说就是,从库将它的查询所依赖的记录版本的信息,以一个事务id来表示,并放在从库发回给主库wal_senderreply中发给主库(见函数具体过程是,在从库,函数XLogWalRcvSendHSFeedback调用GetOldestXmin获得xmin,放入给主库的reply中。主的wal_sender收到后,如果使用了ReplicationSlot,就把这个xmin放入slot的状态信息中,并更新此时系统所有slot的最小xmin。这个系统所有slot的最小xmin怎么在主库传导给vacuum的呢?以自动触发的vacuum操作为例,其中的逻GetSnapshotData(vacuum事务开始时,获取slotxmin,存入全局变量->vacuum_set_xid_limits(调用GetOldestXmin,通过全局变量,获取系统xminslotxmin,取较小值)vacuum_lazy(使用xmin,判断哪些记录版本可以清除)这样,利用ReplicationSlot这个,就解决了从库查询。注意事最后,介绍一下使用ReplicationSlot的注意事项如果收不到从库的reply,ReplicationSlot的状态restartlsn会保持不变,造成主的wal_sender_timeout,及早发现从库断掉的情况。将hot_standby_feedback设为on时,注意如果从库长时间有慢查询发生,可能导致发回到主库的xmin变化较慢,主库的vaccum操作停滞,造成主库被频繁更新的表大小暴增。的9.5版本中看到它大显身手。PgSQL·特性分析·PostgreSQL内核中引入了一个很有意思的插件,pg_prewarm。它可以用于在系统重启时,手动加载经常的表到操作系统的cache或PG的sharedbuffer,从而减少检查系统重启对应用的影响。这个插件是这个通过这个patch加入PG内核的。pg_prewarm的开发者在设计pg_prewarm时,把它设计成一个执行单一任务的工具,尽求简单,所以我们看到的pg_prearm 基本信利用下面的语句可以创建此插件createEXTENSIONcreateEXTENSIONLANGUAGELANGUAGEAS'MODULE_PATHNAME',RETURNSlast_blockint8defaultfirst_blockint8defaultforktextdefaultmodetextdefaultCREATEFUNCTION函数的第一个参数是要做prewarm的表名,第二个参数是prewarm的模式(prefetch模式表示异步预取到操作系统cache;read表示同步预取;buffer则表示同步读入到PGsharedbuffer),第三个参数是relationfork的类型(一般用main,其他类型有visibilitymapfsm,参见[1][2]),最后两个参数是开始和结束的blocknumber(一个表的blocknumber0开始,block总数可以通过pg_class系统表的relpages字段获得)。性能实再来看看,这个prewarm性能上能达到多大效果。我们先将PGsharedbuffer设为总的memory7G。然后创建下面的大小近1G的表||ColumnTablepgbench=#\d+++ |character(20)995 SELECT在每次都清掉操作系统cachePGsharedbuffer的情况下,分别测试下面几种场景不进行pg_prewarm的情况Executiontime:22270.383Executiontime:22270.383Planningtime:0.134(actualtime=0.699..18287.199- SeqScanon (cost=0.00..327389.73time=22270.304..22270.304rows=1 (cost=377389.91..377389.92rows=1width=0)QUERYpgbench=# yzeselectcount(*)from可以看到,近1G的表,全表扫描一遍,耗时22秒多下面我们先做read这种模式的prewarm,test表的数据被同步读入操作系统(pg_prewarm返回的是处理的block数目,此处我们没指定blocknumber,也就是读入test的所有block),然后再做全表扫:Executiontime:8577.831Planningtime:0.049(actualtime=0.086..4716.444 - SeqScanon (cost=0.00..327389.72 time=8577.767..8577.767rows=1 (cost=377389.90..377389.91rows=1width=0)QUERYpgbench=# yzeselectcount(*)frompgbench=#selectpg_prewarm('test','read',时间降至8秒多!这时反复执行全表扫描,时间稳定在8秒多再尝试buffer模式pgbench=#selectpg_prewarm('test','buffer',Executiontime:8214.340Executiontime:8214.340Planningtime:0.049(actualtime=0.015..4250.300 - SeqScanon (cost=0.00..327389.72 time=8214.277..8214.277rows=1 (cost=377389.90..377389.91rows=1width=0)QUERYpgbench=# yzeselectcount(*)fromread模式时间略少,但相差不大。可见,如果操作系统的cache够大,数据取到OS还是sharedbuffer对执行时间影响不大(在不考虑其他应用影响PG的情况下)最后尝试prefetch模式,即异步预取。这里,我们有意在pg_prewarm返回后,立即执行全表查询。这样在执行全表查询时,可能之前的预取还没完成,从而使全表查询和预取并发进行,缩短了总的响应时间:Executiontime:1011.402Executiontime:1011.402Planningtime:0.124time=1011.338..1011.339rows=1 (cost=0.00..0.01rows=1width=0)QUERY yzeselectpg_prewarm('test','prefetch',Executiontime:8420.723Executiontime:8420.723Planningtime:0.344(actualtime=0.065..4583.200 - SeqScanon (cost=0.00..327389.72 time=8420.652..8420.652rows=1 (cost=377389.90..377389.91rows=1width=0)QUERY yzeselectcount(*)from扫描前,做一次异步的prewarm,不失为一种优化全表查询的方法。实pg_prewarm的代码只有一个pg_prewarm.c文件。可以看出,prefetch模式下,对于表的每个block,调用一次PrefetchBuffer,后面的调用为:PrefetchBuffer->smgrprefetch->mdprefetch->FilePrefetch-可见,它是最终调用posix_fadvise,把读请求交给操作系统,然后返回,实现的异步而在read和buffer模式(调用逻辑分别如下)中,最终都调用了系统调用read,来实现同步读入OScachesharedbuffer的(注意buffer模式实际上是先读入OScache,再拷贝到sharedbuffer):readread模式:smgrread->mdread-> ->mdread->FileRead->mdread->FileRead->buffer模式:ReadBufferExtended- mon->smgrread-问可能有人比较疑惑:执行1select*from不就可以将表的数据读入sharedbuffer和OScache而实现预热了吗?岂不是比做这样一个插件更简单?实际上,对于较大的表(大小超过sharedbuff1/4),进行全表扫描时,PG认为没必要为这种操作使用所有sharedbuffer,只会让其使用很少的一部分buffer,一般只有几百K,详细描述可以参见关于BAS_BULKREAD策略的代码和README)。所以,预热大表是不能用一个查询直接实现的,而pg_prewarm正是在这方面大大方便了用户。MySQL·答疑释惑·InnoDB丢失自增背在上一期的月报中,我们在InnoDB自增列重复值问题中提到,InnoDB自增列在重启后会丢失,因为MySQL没有持久化自增值,平时是存在内存表对象中的。如果实例重启的话,内存值丢失,其初始化过程是做了一个类似selectmax(id)1操作。实际上存在另外一种场景,实问题说实例运行过种中,InnoDB表自增值是在表对象中的,表对象又是放在缓存中的,如果表太多而不能全部放在缓存中的话,老的表就会被置换出来,这种被换出来的表下次再使用的时候,就要重新打开一遍,对自增列来说,这个过程就和实例重启类似,需要selectmax(id)+1算InnoDB来说,其数据字典中表对象缓存大小由table_definition_cache系统变量控制,在5.6.8之后,其最小值是400。和表缓存相关的另一个系统变量是table_open_cache,这个控制的是所有线程打开表的缓存大小,这个缓存放在server层。下面我们用testcase的方式来给出InnoDB表对象对置换出的场景letlet500InnoDBSETGLOBALtable_open_cache=SETGLOBALtable_definition_cache=##把table_definition_cache和table_open_cachewhile($i<{--evalCREATETABLEt$i(idINTNOTNULLAUTO_INCREMENT,VARCHAR(30),PRIMARYKEY(id))--evalINSERTINTOt$i(name)--evalALTERTABLEt$iAUTO_INCREMENT=--inc}400letwhile($i<{--evalSELECT*FROM--inc}sleepmysqld(t0..t99)sleept1SHOWCREATETABLE Create)ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTPRIMARYKEY`name`varchar(30)DEFAULT`id`int(11)NOTNULLCREATETABLE`t1`可以看到自增值确实和重启场景一样,本应是100,却变成了2(selectmax(id)1)了问题分原因就是缓存不够,导致表对象被换出,下次再用就要重新打开,这里给出调用栈,对代码感将老的table置换出at 0x00000000011cf246indict_make_room_in_cache(max_tables=400,at/path/to/mysql/storage/innobase/dict/dict0dict.cc:1261 0x0000000001083564insrv_master_evict_from_table_cacheat/path/to/mysql/storage/innobase/srv/srv0srv.cc:2017 0x0000000001084022insrv_master_do_idle_tasks()at dict_table_remove_from_cache_lowcbce767dinclone()fromcbce767dinclone()from cc007851instart_thread() 0x000000000108484ainsrv_master_thread(arg=0x0)尝试从缓存加载表对象 dict_table_check_if_in_cache_lowat 0x00000000011cd51ain(table_name=0x2adef847db20"test/t1",dict_locked=0,ignore_err=DICT_ERR_IGNORE_NONE) e58d8ainha_innobase::open "./test/t1",mode=2,at 0x000000000068668binhandler::ha_open table_arg=0x2adef742bc00, "./test/t1",test_if_locked=2)at 0x00000000009c2a84inmysqld_show_create)"showcreatetablet1",#110x0000000000963bbeinmysql_parse(thd=0x2adef47aa000)at#100x00000000009553b1at缓存加载不到表对象,用selectmaxt逻辑初始化自增 row_search_max_autoinccol_name=0x2b241d855519"id",at e58998inat e59bd9inha_innobase::openname=0x2b241d853780"./test/t1",mode=2,at 0x000000000068668binhandler::ha_opentable_arg=0x2b241e422000,name=0x2b241d853780"./test/t1",parser_state=0x2b241e880630)rawbuf=0x2b241d820010"showcreatetablet1",#110x0000000000963bbeinmysql_parse(thd=0x2b241abaa000)at#100x00000000009553b1at 0x00000000009c2a84inmysqld_show_createtest_if_locked=2)at处理建对于这个问题,一种解决方法是从源码改进,将自增值持久化,可以参考上期的月报给出的思路;如果不想改代码的话,可以这样绕过:在设定auto_increment值后,主动插入一行记录,这样不论在重启还是缓存淘汰的情况下,重新打开表仍能得到预期的值。MySQL·答疑释惑·5.5和5.6时间类型兼容问问题描6.4及以上版本,datetime,time,timestampBinlog5.6.4以下的备库无法执行5.6.16(主库)createtablet1(tdatetimedefaultnow());insertintot15.5.18(备库)showslavestauts\G此时备库中断,报错:Last_Errno:描述信息:Last_Error:Column1oftablet1.t'cannotbeconvertedfromtype'<unknowntype>'totype'datetime'详情见问题原5.5版本的是datetime,time,timestamp这三种数据类型的长整型的数据,insert时的BT为:#0TIME_to_ulonglong_datetime(my_time=0x2ad2c82e84c0)#0TIME_to_ulonglong_datetime(my_time=0x2ad2c82e84c0)/u01/workplace/Percona- #10x0000000000680b6dinField_datetime::storefrom=0x2ad2d0014fe0"2014-02-2511:20:42",len=19,optimized#20x00000000005488a4infill_record(thd=0xa602190,optimizedout>,values=<valueoptimizedout>,optimizedout>,triggers=0x0,#3fill_record_n_invoke_before_triggers(thd=0xa602190,optimizedout>,values=<valueoptimizedout>,optimizedout>,triggers=0x0,5.6.16的相应堆栈为#30x000000000091191a#30x000000000091191a)(this=0x7fa88005dec0,ltime=0x7fa8d42018f0,#20x000000000091553ain(this=0x7fa88005dec0,#10x00000000009155d4inptr=0x7fa88005dea1"\231\222\062\265*",#0my_datetime_packed_to_binary (this=0x7fa88005dec0, #40x00000000009109e9inField_temporal::store f8"2014-02-2511:20:42",len=19,#50x000000000065360bin ,field=0x7fa88005dec0, #60x0000000000663ef6in ,field=0x7fa88005dec0,#70x000000000077bbc6infill_recordptr=0x7fa88005deb8,values=...,ignore_errors=false,#80x000000000077bcf7in(thd=0x6f24020,ptr=0x7fa88005deb0,ignore_errors=false,triggers=0x0,从面的两个堆栈可以看出,在构造插入数据的时候,调用的是Field的具体函数,根据不同类型调用的方法不同;5.55.6之间,datetime的数据类型不一致,当5.5升级到5.6时,其堆栈不变,原因是在表的FRM中,记录了表中列的数据类型,其中5.5中的数据类型为MYSQL_TYPE_DATETIME,5.6的数据类型为MYSQL_TYPE_DATETIME2,所以对于原表升级,不影响,但是对于新表中如果含有这三种数据类型的表,到备库就会出现问题,因为5.5中,没有MYSQL_TYPE_DATETIME2这种数据类型。解决方对表的DML操作或DDL操作,都是依赖于表结构而言的,这也是为什么物理5.5升级到5.6后,对于原本含有datetime,time,timestamp这三种类型的表没有影响,但是对于新建的表就会有影响,原因就是对于产生Binlog的操作或引擎的操作的Field来源于FRM文件,所以,当在创建表的时候,如果5.5要使用5.6Binlog,那我们对于DDL含有这三种数据类型的操作,使用5.5可以识别的数据类型:MYSQL_TYPE_DATETIME,而不是MYSQL_TYPE_DATETIME2,这样在MySQL内部的操作过程中就不会有问题,因此我们可以为的数据类型,否则为新的数据类型。TimeStamp与Datetime的区值域不TIMESTAMPhasarangeof'1970-01-0100:00:01'UTCto'2038-01-03:14:07'UTC.DATETIMEThesupportedrangeis'1000-01-0100:00:00'to'9999-12-3123:59:59'TimeStamp带有时区信息,其中TimeStamp在时,将当前时间转化为UTC格式的时间,如时间,现在是2014-03-1523:21:00,那么的会是2014-03-1523:21:00-3600S;取数据的时候会加上当前时区时间。底层的结构不5.5是以longlong类型的,而5.6的格式如下:timestamp:4+max(3);变长,4-7个字节),没Signdatetime:底层(变长,5-8个字节)Total:64bits=8Total:64bits=8(0-24bits(0-6bits(0-6bits(0-5bits(0-5bits17bits (year0-9999,month0-1 (usedwhenonMySQL·捉虫动态·变量修改导致binlog错背MySQL5.6.6版本新加了这样一个参数——log_bin_use_v1_row_events,这个参数用来控制binlogRows_log_event的格式,如果这个值为1的话,就用v1版的Rows_log_event格式(即5.6.6之前的),默认是0,用新的v2版本的格式,更详细看文档。这个参数一般保持默认即可,但是当我们需要搭5.6->5.5这要的主备的时候,就需要把主库的这个值改为1,不然5.5的备库不能正确解析Rows_log_event。最近在使用这个参数的时候发现了一个bug,导致主库binlog写坏,备库中断,报错如下:Last_SQL_Errno:1594Last_SQL_Error:Relaylogreadfailure:Couldnotparserelaylogevententry.Thepossiblereasonsare:themaster'sbinarylogiscorrupted(youcancheckthisbyrunning'mysqlbinlog'onthebinarylog),theslave'srelaylogiscorrupted(youcancheckthisbyrunning'mysqlbinlog'ontherelaylog),anetworkproblem,orabuginthemaster'sorslave'sMySQLcode.Ifyouwanttocheckthemaster'sbinarylogorslave'srelaylog,youwillbeabletoknowtheirnamesbyissuing'SHOWSLAVESTATUS'onthisslave.bug分binlogevent结-variablepart-variablepart-fixedeventevent如上所示,每种binlogevent都可以分为header和body2部分,body又可以分为fixedpart和variablepart,其中eventheader的长度相同并且固定,5.0开始用的v4格式的binlog,其eventheader固定长度为19字节,包含多个字段,具体每个字段的含义可以看这里。eventbodypostheader长度也是固定的,所以叫fixedpart,但是不同类型event这一部分的长度不一样,最后的variablepart就是event的主体了,这个就长度不一了。log_bin_use_v1_row_events这个值的不同,影响的部分就是postheader这里的长度,如果值为1的话,用v1格式,postheader长度是8个字节,如果0,用v2格式,其长度为10。每个Rows_log_eventeventheadertype字节会标明当前eventv1还是v2,试想一下,如果eventheader部分标明是v2,postheader却实际上只有8个字节,或者反过来,eventheader部分标明是v1,postheader10个字节,备库拿到这样的binlog,去尝试解析的时候,就完全凌乱了。为啥会出现这种一个event前后不一致的情况呢,代码编写不严谨在写Rows_log_event(Write/Update/Delete)过程中,有2次用到log_bin_use_v1_row_events这个全局变量,一次是在构造函数处,一次是在写postheader时Rows_log_event::write_data_header(),2次都是直接使用,如果正好在这2次中间,我们执行setgloballog_bin_use_v1_row_events0|1,改变原来的改这个值,就很容易触发这个bug。另外还有点不严谨的是,文档上说这个值是readonly的,实际代码是dynamic的,如果是readonly的话,也就不会触发上面的bug了。bug修修复很简单,把2次全局变量改成一次就好了,Rows_log_event::write_data_header函数里直接使用已经保存的m_type,改法如m_type==DELETE_ROWS_EVENT_V1m_type==DELETE_ROWS_EVENT_V1+m_type==UPDATE_ROWS_EVENT_V1+ if(likely(!(m_type==WRITE_ROWS_EVENT_V1++ if这样改之后,就只会在构造函数中才用到全局变量MariaDB·特性分析·表/表空间加向MariaDB10.1.13(暂未Release)贡献了这个补丁,可以对表/表空间进行加密加密过的表可以防止某些非用户或偷取磁盘然后通过原始数据文件来偷取数据。当然,假设你已经把密钥文件在另一个系统上。但是,使用加密可能会降低差不多10%的性能。目前,只有XtraDB/InnoDB引擎能完全支持加密。MariaDB在InnoDB/XtraDB中支持两种方式的加密Tableencryption(表级加密)只有在创建时指定PAGE_ENCRYPTION=1的表才被加密Tablespaceencryption(表空间加密)当前实例下的任何文件都被加密(包括日志文MariaDB中所有的加密算法都是基于AES的。但是你可以在启动的时候使用--encryption-algorithm=name来指定具体哪种基于AES的加密算法,有这些可选:选描默认值。不进行任何加密建议值。这是大部分欧洲接受的算法一种新的块加密模式 自己开发的,并且在他们的MariaDB实例中使用的法这种加密模式用于内部计数器计算。你可以用它来加密,但它不能提供强性密钥的管为了保护加密过的数据,密钥必须另外保存,不要跟数据文件放在一个地方。默认情况下MariaDB支持两种密钥管理方式,都是以Plugin的方式实现的file_key_management_plugin是一个密钥管理插件,可以从文件中密钥。这个插件有如file_key_management_plugin_filename=path-to-key-file:密钥文件存放的位file_key_management_plugin_filekey:一个可选的Key,用来密钥文件 f配置的例子:file_key_management_plugin_filekey=file_key_management_plugin_filekey=file_key_management_plugin_filename=encryption-algorithm=这个密钥文件(/home/mdb/keys.enc)包含了AES密钥。128,192或256位密钥都可以支持。ID16个字节。一个密钥文件内容的例子如下: 1是密钥的标识,在建表的时候可以指定使用哪个密钥;接着是16字节的ID,最后是一个16字节的AES密钥。密钥标识可以从0~255,但是0号密钥是保留给InnoDB日志文件使用的,不要密钥文件本身同样可以加密,file_key_management_plugin_filekey定义的密钥可以用来解密密钥文件。OpenSSL命令行工具可以用来创建密钥文件。例如:outoutopensslenc–aes-256-cbc–mdsha1–k<initialPwd>–insecretopensslenc–aes-256-c
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- AI技术助力远程医疗的新纪元
- 云计算在医疗信息管理中的角色
- 办公桌椅的北欧色彩搭配艺术
- AI在医疗诊断中的技术难题与解决策略
- 以患者为中心构建道德健康的智慧医疗体系之研究
- 从数据到决策论电子健康记录系统的价值与影响
- 传统草药图案在健康宣传材料中的创新应用
- 石棉绳企业数字化转型与智慧升级战略研究报告
- 利用人工智能与区块链构建智能IP管理系统的探讨与实践案例分享
- 软床及床垫企业县域市场拓展与下沉战略研究报告
- 工程施工服务方案范文
- 《复发性流产诊治专家共识2022》解读
- 重大疾病证明书样本
- 辽宁省协作校2024-2025学年高二化学下学期期中试题
- 2024-2030年中国太空舱酒店行业市场发展分析及前景趋势与投资研究报告
- 埋地塑料排水管道施工
- 劳工及道德体系法律法规清单
- 宽带账号注销委托书
- 婴幼儿发展引导员(三级)理论试题及答案
- 预制梁场建设施工梁场建设规划
- 2024低预应力预制混凝土实心方桩
评论
0/150
提交评论