個人-mysql內(nèi)核我們知道InnoDB使用bufferpool來緩存從磁盤讀取_第1頁
個人-mysql內(nèi)核我們知道InnoDB使用bufferpool來緩存從磁盤讀取_第2頁
個人-mysql內(nèi)核我們知道InnoDB使用bufferpool來緩存從磁盤讀取_第3頁
個人-mysql內(nèi)核我們知道InnoDB使用bufferpool來緩存從磁盤讀取_第4頁
個人-mysql內(nèi)核我們知道InnoDB使用bufferpool來緩存從磁盤讀取_第5頁
免費(fèi)預(yù)覽已結(jié)束,剩余30頁可下載查看

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

MySQL·性能優(yōu)化·InnoDBbufferpoolflush策略漫背我們知道InnoDB使用bufferpool來緩存從磁盤到內(nèi)存的數(shù)據(jù)頁。bufferpool通常由數(shù)個內(nèi)存塊加上一組控制結(jié)構(gòu)體對象組成。內(nèi)存塊的個數(shù)取決于bufferpoolinstance的個了支持bufferpool的動態(tài)調(diào)整大小。Bufferpool的每個內(nèi)存塊通過mmap的方式分配內(nèi)存,因此你會發(fā)現(xiàn),在實例啟動時虛存很高,而物理內(nèi)存很低。這些大片的內(nèi)存塊又按照16KB劃分為多個frame,用于數(shù)據(jù)頁。雖然大多數(shù)情況下bufferpool是以16KB來數(shù)據(jù)頁,但有一種例外:使用壓縮表時,需要在內(nèi)存中同時壓縮頁和解壓頁,對于壓縮頁,使用Binarybuddyallocator算法來分配內(nèi)存空間。例如我們讀入一個8KB的壓縮頁,就從bufferpool中取一個16KBblock,取其中8KB,剩下的8KB放到空閑鏈表上;如果緊跟著另外一個4KB的壓縮頁讀入內(nèi)存,就可以從這8KB中4KB,同時將剩下的4KB放到空閑鏈表上。為了管理bufferpool,每個bufferpoolinstance使用如下幾個鏈表來管理LRU鏈表包含所有讀入內(nèi)存的數(shù)據(jù)頁Flush_list包含被修改過的臟頁unzip_LRU包含所有解壓頁Freelist上存放當(dāng)前空閑的block另外為了避免查詢數(shù)據(jù)頁時掃描LRU,還為每個bufferpoolinstance了一個pagehash,通過spaceidpageno可以直接找到對應(yīng)的page。一般情況下,當(dāng)我們需要讀入一個Page時,首先根據(jù)spaceidpageno找到對應(yīng)的bufferpoolinstance。然后查詢pagehash,如果pagehash中沒有,則表示需要從磁盤。在讀盤前首先我們需要為即將讀入內(nèi)存的數(shù)據(jù)頁分配一個空閑的block。當(dāng)freelist上存在空閑的block時,可以直接從freelist上;如果沒有,就需要從unzip_lru或者lru上page。這里需要遵循一定的原則(參考函數(shù)buf_LRU_scan_and_free_block,首先嘗試從unzip_lru上解壓頁如果沒有,再嘗試從Lru鏈表上PAGEFLUSH,單獨(dú)從Lru上刷掉一個臟頁,然后再重試。Bufferpool中的page被修改后,不是立刻寫入磁盤,而是由線程定時寫入,和大多數(shù)數(shù)據(jù)庫系統(tǒng)一樣,臟頁的寫盤遵循日志先行WAL原則,因此在每個block上都記錄了一個最近被修改時的Lsn,寫數(shù)據(jù)頁時需要確保當(dāng)前寫入日志文件的redo不低于這個Lsn。然而基于WAL原則的刷臟策略可能帶來一個問題:當(dāng)數(shù)據(jù)庫的寫入負(fù)載過高時,產(chǎn)生redolog的速度極快,redolog可能很快到達(dá)同步checkpoint點。這時候需要進(jìn)行刷臟來推進(jìn)Lsn。由于這種行為是由用戶線程在檢查到redolog空間不夠時觸發(fā),大量用戶線程將可能陷入到這段低效PageCleaner線MySQL5.6中,開啟了一個獨(dú)立的pagecleaner線程來進(jìn)行刷lrulistlist。默認(rèn)每隔一秒運(yùn)行一次,5.6版本里提供了一大堆的參數(shù)來控制pagecleaner的行為,包括這里我們不一一介紹,總的來說,如果你發(fā)現(xiàn)redolog推進(jìn)的非???,為了避免用戶線程陷入刷臟,可以通過調(diào)大innodb_io_capacity_max來解決,該參數(shù)限制了每秒刷新的臟頁上限,調(diào)大該值可以增加Pagecleaner線程每秒的工作量。如果你發(fā)現(xiàn)你的系統(tǒng)中freelist不足,總是需要臟頁來獲取空閑的block時,可以適當(dāng)調(diào)大innodb_lru_scan_depth。該參數(shù)表示從每個bufferpoolinstancelru上掃描的深度,調(diào)大該值有助于多釋放些空閑頁,避免用戶線程去做singlepageflush。為了提升擴(kuò)展性和刷臟效率,在5.7.4版本里引入了多個pagecleaner線程,從而達(dá)到并行刷臟的效果。目前Pagecleaner并未和bufferpool綁定,其模型為一個協(xié)調(diào)線程多個工作線程,協(xié)調(diào)線程本身也是工作線程。因此如果innodb_page_cleaners設(shè)置為4,那么就是一個協(xié)調(diào)線程,加3個工作線程,工作方式為生產(chǎn)者-消費(fèi)者。工作隊列長度為bufferpoolinstance的個數(shù),使用一個全局slot數(shù)組表示。slot的狀態(tài)設(shè)置為PAGE_CLEANER_STATE_REQUESTED,并設(shè)置目標(biāo)page數(shù)及l(fā)sn_limit,然后喚醒工作線程(pc_request)工作線程被喚醒后,從slot數(shù)組中取一個未被占用的slot,修改其狀態(tài),表示已被調(diào)度,然后對該slot所對應(yīng)的bufferpoolinstance進(jìn)行操作。直到所有的slot都被消費(fèi)完后,才進(jìn)入下一輪。通過這種方式,多個pagecleaner線程實現(xiàn)了并發(fā)flushbufferpool,從而提升flushdirtypage/lru的效率。MySQL5.7InnoDBflush策略優(yōu)在之前版本中,因為可能同時有多個線程操作bufferpoolpage(在刷臟時會釋放bufferpoolmutex),每次刷完一個page后需要回溯到鏈表尾部,使得掃描bp鏈表的時間復(fù)雜度O(N*N)。5.6版本中針對Flushlist的掃描做了一定的修復(fù),使用一個指針來記錄當(dāng)前正在flush的page,待flush操作完成后,再看一下這個指針有沒有被別的線程修改掉,如果被修改了,就回因此在5.7版本中對這個問題進(jìn)行了徹底的修復(fù),使用多個名為hazardpointer的指針,在需要掃描LIST時,下一個即將掃描的目標(biāo)page,根據(jù)不同的目的分為幾類:flush_hp:用作批量刷FLUSHlru_hp:用作批量刷LRUlru_scan_itr:用于從LRU鏈表上一個可替換的page,總是從上一次掃描結(jié)束的位置開始,而不是LRU尾部single_scan_itr:bufferpool中沒有空閑block時,用戶線程會從FLUSHLIST上單獨(dú)一個可替換的page或者flush一個臟頁,總是從上一次掃描結(jié)束的位置開始,而不是LRU尾部。后兩類的hp都是由用戶線程在嘗試獲取空閑block時調(diào)用,只有在推進(jìn)到某個buf_page_t::old被設(shè)置成truepage大約從Lru鏈表尾部起至總長度的八分之三位置的page)時,再將指針重置到Lru尾部。這些指針在初始化bufferpool時分配,每個bufferpoolinstance都擁有自己的hp指針。當(dāng)某個線程對bufferpool中的page進(jìn)行操作時,例如需要從LRU中移除Page時,如果當(dāng)前的page被設(shè)置為hp,就要將hp更新為當(dāng)前Page的前一個page。當(dāng)完成當(dāng)前page的flush操作后,直接使用hp中的page指針進(jìn)行下一輪flush。社區(qū)優(yōu)一如既往的,PerconaServer5.6版本中針對bufferpoolflush做了不少的優(yōu)化,主優(yōu)化刷LRU流程該函數(shù)由pagecleaner線程調(diào)用原生的邏輯:依次flush每個bufferpoolinstance,每次掃描的深度通過參數(shù)后的邏輯為:每次flush一個bufferpoolLRU時,只刷一個chunk,然后再下一個instance,刷完所有instnace后,再回到前面再刷一個chunk。簡而言之,把集中的flush操作進(jìn)行了分散,其目的是分散壓力,避免對某個instance的集中操作,給予其他線程bufferpool的機(jī)會。允許設(shè)定刷LRU/FLUSHLIST的超時時間,防止flush操作時間過長導(dǎo)致別的線程(例如嘗試做singlepageflush的用戶線程)stall?。划?dāng)?shù)竭_(dá)超時時間時,pagecleaner線程退出flush。避免用戶線程參與刷buffer當(dāng)用戶線程參與刷bufferpool時,由于線程數(shù)的不可控,將產(chǎn)生嚴(yán)重的競爭開銷,例freelist不足時做singlepageflush,以及在redo空間不足時,做dirtyflush,都會嚴(yán)重影響性能。PerconaServer允許選擇讓pagecleaner線程來做這些工作,用戶線程只需要等待即可。出于效率考慮,用戶還可以設(shè)置pagecleaner線程的cpu另外在Pagecleaner線程經(jīng)過優(yōu)化后,可以知道系統(tǒng)當(dāng)前處于同步刷新狀態(tài),可以去做更激烈的刷臟(furiousflush),用戶線程參與到其中,可能只會起到反作用。允許設(shè)置pagecleaner線程,purge線程,io線程,master線程的CPU調(diào)度優(yōu)先級,并優(yōu)先獲得InnoDBmutex。使用新的獨(dú)立線程來刷bufferpool的LRU鏈表,將這部分工作負(fù)擔(dān)從page線程剝離的強(qiáng)化線程,讓用戶線程少參與到刷臟/checkpoint這類耗時操作中。MySQL·社區(qū)動態(tài)·5.6.23InnoDB相關(guān)本節(jié)了MySQL5.6.23的幾個和InnoDB相關(guān)的主要bugfix,簡單闡述下問題及解決方案問題當(dāng)執(zhí)行FLUSHTABLE..FOREXPORT命令時,會暫停purge線程的操作。這一步通過設(shè)置一個標(biāo)記purge_sys->state的值為PURGE_STATE_STOP來告訴purge線程該停下來歇歇了。然而如果Purge線程當(dāng)前正在函數(shù)srv_do_purge中工作,該函數(shù)會執(zhí)行一個while循環(huán),退出條件是當(dāng)前servershutdown,或者上次purgepage數(shù)為0,并沒有檢查purge線程的狀態(tài)是否被設(shè)置為PURGE_STATE_STOP;很顯然,如果當(dāng)前的historylist非常長,那么可能需要等待purge完成后,才能退出循環(huán),而在用戶看來,就好像hang了很久一樣。推長historylist很容易:開啟一個打開readview的事務(wù)(例如RR級別下執(zhí)行一個SELECT)不做提交,同時有并發(fā)的DML,跑一段時間historylist就上去了。解在函數(shù)srv_do_purge函數(shù)的while退出條件中加上purge線程狀態(tài)判斷,如果被設(shè)置問題在執(zhí)行InnoDBcrashrecovery階段,如果發(fā)現(xiàn)不合法的大字段,就會去調(diào)用函數(shù)ib_warn_row_too_big去打印一條warning,函數(shù)為push_warning_printf。然而這個函數(shù)的目的是給客戶端返回一條warning,而這時候系統(tǒng)還在恢復(fù)階段,并沒有合法的thd對束。早期版本5.6及之前的版本,我們可以定義非常大的blob字段,但如果字段太長,對這些字段的修改,可能導(dǎo)致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空間是否足夠,如果不夠用,就推進(jìn)checkpoint點。解不調(diào)用push_warning_printf。補(bǔ)問題當(dāng)我們通過alter語句修改一個被外鍵約束的列名時,由于沒有從數(shù)據(jù)詞典cache中將包含老列名的cache項掉,導(dǎo)致重載外鍵約束時失敗。舉個簡單的例子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列已經(jīng)被renameid,但打印出來的信息也并沒有更正。當(dāng)被外鍵約束的列名被修改時,將對應(yīng)的外鍵項從數(shù)據(jù)詞典cache中,當(dāng)其被隨后重新加載時補(bǔ)問題如上文所提到的,在新版本InnoDB中,對blob字段的數(shù)據(jù)操作需要保證其不超過總的redologfile大小的十分之一,但是返回的錯誤碼DB_TOO_BIG_RECORD及打印的信息太容易讓人mayhelp.Incurrentrowformat,BLOBprefixof768bytesisTEXTorBLOBorusingROW_FORMAT=DYNAMICERROR42000:Rowsizetoolarge(>####).Changingsomecolumns解輸出更合適、更直觀的錯誤信息,如下usingusingisgreaterthan10%ofredologsize.IncreasetheredologERROR42000:ThesizeofBLOB/TEXTdatainsertedinone補(bǔ)問題FLUSHTABLE操作在某些情況下可能導(dǎo)致實例crash如如下執(zhí)行序列mysql>FLUSHTABLEmysql>FLUSHTABLE QueryOK,0rowsaffected(0.00mysql>LOCKTABLESt1WRITE,t1ASt0READ,t1ASt2QueryOK,0rowsaffected(0.00mysql>CREATETABLEt1(CTEXTCHARACTERSETUJIS)當(dāng)執(zhí)行FLUSHTABLE時,在重載表cache時,InnoDB層會針對每個表設(shè)置其狀(ha_innobase::store_lock)。如果執(zhí)行FLUSH操作,并且加的是讀鎖時,就會調(diào)用函數(shù)row_quiesce_set_state將table->quiesce設(shè)置為QUIESCE_START。在上例中,表t1的兩個表名表均加讀鎖,造成重復(fù)設(shè)置狀態(tài)為QUIESCE_START,導(dǎo)致斷言失敗。Tips:在5.6版本中,雖然有明確的FLUSHTABLE..FOREXPORT命令來協(xié)助轉(zhuǎn)儲ibd文件。但實際上,簡單的FLUSHTABLE操作默認(rèn)就會產(chǎn)生一個tbname.cfg的配置文件,拷貝該文件和ibd,可以將數(shù)據(jù)轉(zhuǎn)移到其他實例上。table->quiesce用于標(biāo)識操作狀態(tài),例如,如果標(biāo)識為QUIESCE_START,就會在函數(shù)ha_innobase::external_lock中調(diào)用row_quiesce_table_start來啟動配置文件的生成。解問題線上實例錯誤日志中偶爾出現(xiàn)“UNABLETOPURGEARECORD”,從bug系統(tǒng)來看,很多用當(dāng)changebuffer模塊以如下序列來緩存索引操作時可能產(chǎn)生上述錯誤信息記錄被標(biāo)記刪除隨后插入相同記錄--Purge線程需要物理刪除二級索引記錄,操作被buffer--當(dāng)讀入物理頁時,總是需要進(jìn)行ibufmerge。如果執(zhí)行到IBUF_OP_DELETE這種類型changebuffer時,發(fā)現(xiàn)記錄并沒有被標(biāo)記刪除,就會導(dǎo)致錯誤日志報錯為了搞清楚邏輯,我們簡單的理一下相關(guān)代碼pool的控制結(jié)構(gòu)體中,有一個成員buf_pool->watch[BUF_POOL_WATCH_SIZE],BUF_POOL_WATCH_SIZE的值為purge線程個數(shù),用于輔助Purge操作。假定內(nèi)存中沒有對應(yīng)的Page,Purge線程會做如下幾件事兒首先查詢bufferpool,看看page是否已經(jīng)讀入內(nèi)存;如果不在內(nèi)存中,則將pageno等信息到watch數(shù)組中,并插入pagehash(buf_pool_watch_set)。(如果隨后page被讀入內(nèi)存,也會刪除watch標(biāo)記)集索引記錄沒有deletemark并且其trxid比當(dāng)前的purgeview還舊時,不可以做Purge操作)隨后在插入IBUF_OP_DELETE類型的ibuf記錄時,還會doublecheck下該page是否被設(shè)為sentinel(ibuf_insert_low,buf_pool_watch_occurred),如果未被設(shè)置,表明已經(jīng)page已經(jīng)讀入內(nèi)存,就可以直接去做purge,而無需緩存了。對于普通的操作類型,例如IBUF_OP_INSERTIBUF_OP_DELETE_MARK,同樣也會doublecheckpage是否讀入了內(nèi)存。在函數(shù)ibuf_insert中會調(diào)用buf_page_hash_get進(jìn)行檢查,如果page被讀入內(nèi)存,則不緩存操作,如果請求的Page被設(shè)為sentinel,則從buf_page_hash_get返回NULL,因此隨后判定需要緩存該類型的標(biāo)記刪除記錄,寫入Purge線程設(shè)置page對應(yīng)的sentinel,完成檢查,準(zhǔn)備調(diào)用插入相同記錄,寫入Purge線程寫入解如果記錄所在的page被設(shè)置了一個sentinel,那么對該page的并發(fā)插入操作就不應(yīng)該緩存changebuffer中,而是直接去嘗試物理頁。問題解問題在恢復(fù)后立刻執(zhí)行一次slowshutdown(innodb_fast_shutdown=0)可能產(chǎn)生斷言失敗crash。原因是當(dāng)完成crashrecovery后,對于需要回滾的事務(wù),會起單獨(dú)的線程來執(zhí)行,這時候如果shutdown實例,會觸發(fā)觸發(fā)purge線程內(nèi)部斷言失敗:ut_a(n_pages_purged==0||srv_fast_shutdown!=0);解等待trx_rollback_or_clean_all_recovered完成后,再進(jìn)行slow補(bǔ)PgSQL·特性分析·ReplicationPostgreSQL9.4已于2014年底正式發(fā)布了(阿里云的RDS將支持PG9.4)。在這個版本,我們看到了像Jsonb,LogicalDecoding,ReplicationSlot等新功能。對于ReplicationSlot,文檔上介紹的不多,乍一看讓人比較難理解是做什么的。其實,ReplicationSlot的出現(xiàn),主要是為最終在PG內(nèi)核實現(xiàn)邏輯和雙向鋪路的(目前,邏輯和雙向在內(nèi)核中還缺少很多功能點,需要借助BDR插件,見PGwiki,引入ReplicationSlot的背后原因見這里)。不過,作為9.4版本的一個主要功能,它不但可以用于邏輯,還可用于物理(或者叫StreamingReplication)。針對物理的ReplicationSlot稱為PhysicalReplicationSlot。由于大家目前主要用的還只是PG自帶的物理方式,我們就重點分析一下PhysicalReplicationSlot。使用PhysicalReplicationSlot,可以達(dá)到兩個效果 requestedWALsegment00000001000000010000002Dhasbeen通過ReplicationSlot記錄的從庫狀態(tài),PG會保證從庫還沒有apply的日志,不會從主庫的 里面清除或archive掉。而且,replicationslot的狀態(tài)信息是持久化保存的,即便當(dāng)允許應(yīng)用連接從庫做只讀查詢時,ReplicationSlot可以與運(yùn)行時參數(shù) cancelingstatementdue with下面看看PhysicalReplicationSlot的用法和內(nèi)核實現(xiàn)用下面是啟用ReplicationSlot的步驟,很簡單 ingReplication的主庫從庫。涉及的參數(shù)有,listen_addresses(='*'),hot_standby(=on),wal_level(=hot_standby),max_wal_senders(=1),尤其注意配置max_replication_slots大于等于1。這些參數(shù)在主庫創(chuàng)建replication++my_rep_slot_1 |postgres=#SELECT*此時replicationslot還不處于active狀態(tài)3)在從庫配置recovery.conf如下,然后重啟從庫pression=1pression=1primary_conninfo='user=pg001host=10.x.x.xprimary_slot_name=standby_mode=觀察主庫replicationslot的狀態(tài)變化+++++++++|1812|||| my_rep_slot_1xmin|catalog_xmin| |plugin|slot_type|datoid|database|activepostgres=#SELECT*FROM 與hot_standby_feedback配合使用。在將從庫的postgresql.conf文件中hot_standby_feedback選項設(shè)為on,重啟從庫即可內(nèi)核實replicationslot是由下面的patch加入內(nèi)核中的(22:45-(22:45-Sat,1Feb2014RobertHaas(22:45-Sat,1Feb2014authorRobertHaas Slotshavesomeadvantagesovertechniques,asexplainedinhot_standby_feedback=on)pruningoftupleswhoseremovalwouldwrite-aheadlogsegmentsneededbyastandby,aswellasoneitheramasterorastandbytopreventprematureremovalReplicationslotsareacrash-safedatastructurewhichcan這個patch改的文件不少,分析這些代碼,我們重點關(guān)注下面的問題A)ReplicationSlot是如何在內(nèi)核中創(chuàng)建的通過分析創(chuàng)建ReplicationSlot時調(diào)用的函數(shù)ReplicationSlotCreate,可以看出,ReplicationSlot實質(zhì)上是內(nèi)存中的一些數(shù)據(jù)結(jié)構(gòu),加上持久化保存到pg_replslot/<slot 中的二進(jìn)制狀態(tài)文件。在PG啟動的時候,預(yù)先在共享內(nèi)存中分配好這些數(shù)據(jù)結(jié)構(gòu)所用內(nèi)存(即一個大小為max_replication_slots的數(shù)組)。這些數(shù)據(jù)結(jié)構(gòu)在用戶創(chuàng)建ReplicationSlot時開始被使用。一個ReplicationSlot被創(chuàng)建并使用后,其數(shù)據(jù)結(jié)構(gòu)和狀態(tài)文件會被WAL(Write-Ahead-Log)的發(fā)送者(wal_sender)進(jìn)程更新。另外,如果單純從ReplicationSlot的名字,我們很容易覺得ReplicationSlot會創(chuàng)建新ReplicationSlot還是使用了wal_sender原有連接(由于一個從庫一個wal_sender連接,所以一個從庫和主庫之間也只有一個active的ReplicationSlot)。ReplicationSlot的狀態(tài)是如何被更新的很容易發(fā)現(xiàn),ReplicationSlot的狀態(tài)的更新有兩種情況第一種是在ProcessStandbyHSFeedbackMessage這個函數(shù)被更新。這個函數(shù)是在處理wal_sender所收到的從庫發(fā)回的feedbackreplymessage時調(diào)用的。通過這個函數(shù),我們可以看出,每個wal_sender進(jìn)程的ReplicationSlot(就是用戶創(chuàng)建的ReplicationSlot)保存在MyReplicationSlot這個全局變量中。在處理從庫發(fā)回的reply時,reply中的xmin信息會被提取出來,存入slotdata.xmineffective_xmin域中,并通過函數(shù)ProcArraySetReplicationSlotXmin,最終更新到系統(tǒng)全局的這里要注意,如果我們有多個ReplicationSlot(分別對應(yīng)各自從庫),則在更新全局結(jié)構(gòu)procArray->replication_slot_xmin時,會選取所有slot中最小的xmin值。第二種是在ProcessStandbyReplyMessage中。這個函數(shù)處理從庫發(fā)送的restartlsn信(即從庫apply的日志的編號),會直接將其更新到replicationslotrestartlsn域中,并保存到磁盤,用于主庫判斷是否要保留日志不被archive。ReplicationSlot如何和hot_standby_feedback配合,來避免從庫的查詢的這里,從庫的查詢指的是下面的情況:從庫上有正在運(yùn)行的查詢,而且運(yùn)行時間很長;這時主庫上在做正常的vaccum,清除掉無用的記錄版本。但主庫的vaccum是不知道從庫的查詢存在的,所以在清除時,不考慮從庫的正在運(yùn)行的查詢,只考慮主庫里面的事務(wù)狀態(tài)。其結(jié)果,vacuum可能會清除掉從庫查詢中涉及的,仍然在使用的記錄版本。當(dāng)這些vaccum操作,通過日志同步到從庫,而恰好從庫的查詢?nèi)匀粵]有運(yùn)行完,vaccum就要等待或cancel這個查詢,以保證同步正常繼續(xù)和查詢不出現(xiàn)錯誤的結(jié)果。這樣,每當(dāng)用戶在從庫運(yùn)行長查詢,就容易出現(xiàn)我們上面提到到queryerror。如何避免這種呢?目前最好的解決方案是使用hot_standby_feedback+ReplicationSlot。其原理簡單說就是,從庫將它的查詢所依賴的記錄版本的信息,以一個事務(wù)id來表示,并放在從庫發(fā)回給主庫wal_senderreply中發(fā)給主庫(見函數(shù)具體過程是,在從庫,函數(shù)XLogWalRcvSendHSFeedback調(diào)用GetOldestXmin獲得xmin,放入給主庫的reply中。主的wal_sender收到后,如果使用了ReplicationSlot,就把這個xmin放入slot的狀態(tài)信息中,并更新此時系統(tǒng)所有slot的最小xmin。這個系統(tǒng)所有slot的最小xmin怎么在主庫傳導(dǎo)給vacuum的呢?以自動觸發(fā)的vacuum操作為例,其中的邏GetSnapshotData(vacuum事務(wù)開始時,獲取slotxmin,存入全局變量->vacuum_set_xid_limits(調(diào)用GetOldestXmin,通過全局變量,獲取系統(tǒng)xminslotxmin,取較小值)vacuum_lazy(使用xmin,判斷哪些記錄版本可以清除)這樣,利用ReplicationSlot這個,就解決了從庫查詢。注意事最后,介紹一下使用ReplicationSlot的注意事項如果收不到從庫的reply,ReplicationSlot的狀態(tài)restartlsn會保持不變,造成主的wal_sender_timeout,及早發(fā)現(xiàn)從庫斷掉的情況。將hot_standby_feedback設(shè)為on時,注意如果從庫長時間有慢查詢發(fā)生,可能導(dǎo)致發(fā)回到主庫的xmin變化較慢,主庫的vaccum操作停滯,造成主庫被頻繁更新的表大小暴增。的9.5版本中看到它大顯身手。PgSQL·特性分析·PostgreSQL內(nèi)核中引入了一個很有意思的插件,pg_prewarm。它可以用于在系統(tǒng)重啟時,手動加載經(jīng)常的表到操作系統(tǒng)的cache或PG的sharedbuffer,從而減少檢查系統(tǒng)重啟對應(yīng)用的影響。這個插件是這個通過這個patch加入PG內(nèi)核的。pg_prewarm的開發(fā)者在設(shè)計pg_prewarm時,把它設(shè)計成一個執(zhí)行單一任務(wù)的工具,盡求簡單,所以我們看到的pg_prearm 基本信利用下面的語句可以創(chuàng)建此插件createEXTENSIONcreateEXTENSIONLANGUAGELANGUAGEAS'MODULE_PATHNAME',RETURNSlast_blockint8defaultfirst_blockint8defaultforktextdefaultmodetextdefaultCREATEFUNCTION函數(shù)的第一個參數(shù)是要做prewarm的表名,第二個參數(shù)是prewarm的模式(prefetch模式表示異步預(yù)取到操作系統(tǒng)cache;read表示同步預(yù)??;buffer則表示同步讀入到PGsharedbuffer),第三個參數(shù)是relationfork的類型(一般用main,其他類型有visibilitymapfsm,參見[1][2]),最后兩個參數(shù)是開始和結(jié)束的blocknumber(一個表的blocknumber0開始,block總數(shù)可以通過pg_class系統(tǒng)表的relpages字段獲得)。性能實再來看看,這個prewarm性能上能達(dá)到多大效果。我們先將PGsharedbuffer設(shè)為總的memory7G。然后創(chuàng)建下面的大小近1G的表||ColumnTablepgbench=#\d+++ |character(20)995 SELECT在每次都清掉操作系統(tǒng)cachePGsharedbuffer的情況下,分別測試下面幾種場景不進(jìn)行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表的數(shù)據(jù)被同步讀入操作系統(tǒng)(pg_prewarm返回的是處理的block數(shù)目,此處我們沒指定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秒多!這時反復(fù)執(zhí)行全表掃描,時間穩(wěn)定在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模式時間略少,但相差不大??梢姡绻僮飨到y(tǒng)的cache夠大,數(shù)據(jù)取到OS還是sharedbuffer對執(zhí)行時間影響不大(在不考慮其他應(yīng)用影響PG的情況下)最后嘗試prefetch模式,即異步預(yù)取。這里,我們有意在pg_prewarm返回后,立即執(zhí)行全表查詢。這樣在執(zhí)行全表查詢時,可能之前的預(yù)取還沒完成,從而使全表查詢和預(yù)取并發(fā)進(jìn)行,縮短了總的響應(yīng)時間: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,不失為一種優(yōu)化全表查詢的方法。實pg_prewarm的代碼只有一個pg_prewarm.c文件。可以看出,prefetch模式下,對于表的每個block,調(diào)用一次PrefetchBuffer,后面的調(diào)用為:PrefetchBuffer->smgrprefetch->mdprefetch->FilePrefetch-可見,它是最終調(diào)用posix_fadvise,把讀請求交給操作系統(tǒng),然后返回,實現(xiàn)的異步而在read和buffer模式(調(diào)用邏輯分別如下)中,最終都調(diào)用了系統(tǒng)調(diào)用read,來實現(xiàn)同步讀入OScachesharedbuffer的(注意buffer模式實際上是先讀入OScache,再拷貝到sharedbuffer):readread模式:smgrread->mdread-> ->mdread->FileRead->mdread->FileRead->buffer模式:ReadBufferExtended- mon->smgrread-問可能有人比較疑惑:執(zhí)行1select*from不就可以將表的數(shù)據(jù)讀入sharedbuffer和OScache而實現(xiàn)預(yù)熱了嗎?豈不是比做這樣一個插件更簡單?實際上,對于較大的表(大小超過sharedbuff1/4),進(jìn)行全表掃描時,PG認(rèn)為沒必要為這種操作使用所有sharedbuffer,只會讓其使用很少的一部分buffer,一般只有幾百K,詳細(xì)描述可以參見關(guān)于BAS_BULKREAD策略的代碼和README)。所以,預(yù)熱大表是不能用一個查詢直接實現(xiàn)的,而pg_prewarm正是在這方面大大方便了用戶。MySQL·答疑釋惑·InnoDB丟失自增背在上一期的月報中,我們在InnoDB自增列重復(fù)值問題中提到,InnoDB自增列在重啟后會丟失,因為MySQL沒有持久化自增值,平時是存在內(nèi)存表對象中的。如果實例重啟的話,內(nèi)存值丟失,其初始化過程是做了一個類似selectmax(id)1操作。實際上存在另外一種場景,實問題說實例運(yùn)行過種中,InnoDB表自增值是在表對象中的,表對象又是放在緩存中的,如果表太多而不能全部放在緩存中的話,老的表就會被置換出來,這種被換出來的表下次再使用的時候,就要重新打開一遍,對自增列來說,這個過程就和實例重啟類似,需要selectmax(id)+1算InnoDB來說,其數(shù)據(jù)字典中表對象緩存大小由table_definition_cache系統(tǒng)變量控制,在5.6.8之后,其最小值是400。和表緩存相關(guān)的另一個系統(tǒng)變量是table_open_cache,這個控制的是所有線程打開表的緩存大小,這個緩存放在server層。下面我們用testcase的方式來給出InnoDB表對象對置換出的場景l(fā)etlet500InnoDBSETGLOBALtable_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`可以看到自增值確實和重啟場景一樣,本應(yīng)是100,卻變成了2(selectmax(id)1)了問題分原因就是緩存不夠,導(dǎo)致表對象被換出,下次再用就要重新打開,這里給出調(diào)用棧,對代碼感將老的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處理建對于這個問題,一種解決方法是從源碼改進(jìn),將自增值持久化,可以參考上期的月報給出的思路;如果不想改代碼的話,可以這樣繞過:在設(shè)定auto_increment值后,主動插入一行記錄,這樣不論在重啟還是緩存淘汰的情況下,重新打開表仍能得到預(yù)期的值。MySQL·答疑釋惑·5.5和5.6時間類型兼容問問題描6.4及以上版本,datetime,time,timestampBinlog5.6.4以下的備庫無法執(zhí)行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這三種數(shù)據(jù)類型的長整型的數(shù)據(jù),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的相應(yīng)堆棧為#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,從面的兩個堆??梢钥闯觯跇?gòu)造插入數(shù)據(jù)的時候,調(diào)用的是Field的具體函數(shù),根據(jù)不同類型調(diào)用的方法不同;5.55.6之間,datetime的數(shù)據(jù)類型不一致,當(dāng)5.5升級到5.6時,其堆棧不變,原因是在表的FRM中,記錄了表中列的數(shù)據(jù)類型,其中5.5中的數(shù)據(jù)類型為MYSQL_TYPE_DATETIME,5.6的數(shù)據(jù)類型為MYSQL_TYPE_DATETIME2,所以對于原表升級,不影響,但是對于新表中如果含有這三種數(shù)據(jù)類型的表,到備庫就會出現(xiàn)問題,因為5.5中,沒有MYSQL_TYPE_DATETIME2這種數(shù)據(jù)類型。解決方對表的DML操作或DDL操作,都是依賴于表結(jié)構(gòu)而言的,這也是為什么物理5.5升級到5.6后,對于原本含有datetime,time,timestamp這三種類型的表沒有影響,但是對于新建的表就會有影響,原因就是對于產(chǎn)生Binlog的操作或引擎的操作的Field來源于FRM文件,所以,當(dāng)在創(chuàng)建表的時候,如果5.5要使用5.6Binlog,那我們對于DDL含有這三種數(shù)據(jù)類型的操作,使用5.5可以識別的數(shù)據(jù)類型:MYSQL_TYPE_DATETIME,而不是MYSQL_TYPE_DATETIME2,這樣在MySQL內(nèi)部的操作過程中就不會有問題,因此我們可以為的數(shù)據(jù)類型,否則為新的數(shù)據(jù)類型。TimeStamp與Datetime的區(qū)值域不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帶有時區(qū)信息,其中TimeStamp在時,將當(dāng)前時間轉(zhuǎn)化為UTC格式的時間,如時間,現(xiàn)在是2014-03-1523:21:00,那么的會是2014-03-1523:21:00-3600S;取數(shù)據(jù)的時候會加上當(dāng)前時區(qū)時間。底層的結(jié)構(gòu)不5.5是以longlong類型的,而5.6的格式如下:timestamp:4+max(3);變長,4-7個字節(jié)),沒Signdatetime:底層(變長,5-8個字節(jié))Total:64bits=8Total:64bits=8(0-24bits(0-6bits(0-6bits(0-5bits(0-5bits17bits (year0-9999,month0-1 (usedwhenonMySQL·捉蟲動態(tài)·變量修改導(dǎo)致binlog錯背MySQL5.6.6版本新加了這樣一個參數(shù)——log_bin_use_v1_row_events,這個參數(shù)用來控制binlogRows_log_event的格式,如果這個值為1的話,就用v1版的Rows_log_event格式(即5.6.6之前的),默認(rèn)是0,用新的v2版本的格式,更詳細(xì)看文檔。這個參數(shù)一般保持默認(rèn)即可,但是當(dāng)我們需要搭5.6->5.5這要的主備的時候,就需要把主庫的這個值改為1,不然5.5的備庫不能正確解析Rows_log_event。最近在使用這個參數(shù)的時候發(fā)現(xiàn)了一個bug,導(dǎo)致主庫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結(jié)-variablepart-variablepart-fixedeventevent如上所示,每種binlogevent都可以分為header和body2部分,body又可以分為fixedpart和variablepart,其中eventheader的長度相同并且固定,5.0開始用的v4格式的binlog,其eventheader固定長度為19字節(jié),包含多個字段,具體每個字段的含義可以看這里。eventbodypostheader長度也是固定的,所以叫fixedpart,但是不同類型event這一部分的長度不一樣,最后的variablepart就是event的主體了,這個就長度不一了。log_bin_use_v1_row_events這個值的不同,影響的部分就是postheader這里的長度,如果值為1的話,用v1格式,postheader長度是8個字節(jié),如果0,用v2格式,其長度為10。每個Rows_log_eventeventheadertype字節(jié)會標(biāo)明當(dāng)前eventv1還是v2,試想一下,如果eventheader部分標(biāo)明是v2,postheader卻實際上只有8個字節(jié),或者反過來,eventheader部分標(biāo)明是v1,postheader10個字節(jié),備庫拿到這樣的binlog,去嘗試解析的時候,就完全凌亂了。為啥會出現(xiàn)這種一個event前后不一致的情況呢,代碼編寫不嚴(yán)謹(jǐn)在寫Rows_log_event(Write/Update/Delete)過程中,有2次用到log_bin_use_v1_row_events這個全局變量,一次是在構(gòu)造函數(shù)處,一次是在寫postheader時Rows_log_event::write_data_header(),2次都是直接使用,如果正好在這2次中間,我們執(zhí)行setgloballog_bin_use_v1_row_events0|1,改變原來的改這個值,就很容易觸發(fā)這個bug。另外還有點不嚴(yán)謹(jǐn)?shù)氖?,文檔上說這個值是readonly的,實際代碼是dynamic的,如果是readonly的話,也就不會觸發(fā)上面的bug了。bug修修復(fù)很簡單,把2次全局變量改成一次就好了,Rows_log_event::write_data_header函數(shù)里直接使用已經(jīng)保存的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這樣改之后,就只會在構(gòu)造函數(shù)中才用到全局變量MariaDB·特性分析·表/表空間加向MariaDB10.1.13(暫未Release)貢獻(xiàn)了這個補(bǔ)丁,可以對表/表空間進(jìn)行加密加密過的表可以防止某些非用戶或偷取磁盤然后通過原始數(shù)據(jù)文件來偷取數(shù)據(jù)。當(dāng)然,假設(shè)你已經(jīng)把密鑰文件在另一個系統(tǒng)上。但是,使用加密可能會降低差不多10%的性能。目前,只有XtraDB/InnoDB引擎能完全支持加密。MariaDB在InnoDB/XtraDB中支持兩種方式的加密Tableencryption(表級加密)只有在創(chuàng)建時指定PAGE_ENCRYPTION=1的表才被加密Tablespaceencryption(表空間加密)當(dāng)前實例下的任何文件都被加密(包括日志文MariaDB中所有的加密算法都是基于AES的。但是你可以在啟動的時候使用--encryption-algorithm=name來指定具體哪種基于AES的加密算法,有這些可選:選描默認(rèn)值。不進(jìn)行任何加密建議值。這是大部分歐洲接受的算法一種新的塊加密模式 自己開發(fā)的,并且在他們的MariaDB實例中使用的法這種加密模式用于內(nèi)部計數(shù)器計算。你可以用它來加密,但它不能提供強(qiáng)性密鑰的管為了保護(hù)加密過的數(shù)據(jù),密鑰必須另外保存,不要跟數(shù)據(jù)文件放在一個地方。默認(rèn)情況下MariaDB支持兩種密鑰管理方式,都是以Plugin的方式實現(xiàn)的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個字節(jié)。一個密鑰文件內(nèi)容的例子如下: 1是密鑰的標(biāo)識,在建表的時候可以指定使用哪個密鑰;接著是16字節(jié)的ID,最后是一個16字節(jié)的AES密鑰。密鑰標(biāo)識可以從0~255,但是0號密鑰是保留給InnoDB日志文件使用的,不要密鑰文件本身同樣可以加密,file_key_management_plugin_filekey定義的密鑰可以用來解密密鑰文件。OpenSSL命令行工具可以用來創(chuàng)建密鑰文件。例如:outoutopensslenc–aes-256-cbc–mdsha1–k<initialPwd>–insecretopensslenc–aes-256-c

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論