為什麼MySQL不建議delete刪除數據

2021-01-10 計算機java編程

我負責的有幾個系統隨著業務量的增長,存儲在MySQL中的數據日益劇增,我當時就想現在的業務方不講武德,搞偷襲,趁我沒反應過來把很多表,很快,很快啊都打到了億級別,我大意了,沒有閃,這就導致跟其Join的表的SQL變得很慢,對的應用接口的response time也變長了,影響了用戶體驗。

事後我找到業務方,我批評了他們跟他們說要講武德,連忙跟我道歉,這個事情才就此作罷,走的時候我對他們說下次不要這樣了,耗子尾汁,好好反思。

罵歸罵,事情還是得解決,時候我分析原因發現,發現有些表的數據量增長很快,對應SQL掃描了很多無效數據,導致SQL慢了下來,通過確認之後,這些大表都是一些流水、記錄、日誌類型數據,只需要保留1到3個月,此時需要對表做數據清理實現瘦身,一般都會想到用insert + delete的方式去清理。

這篇文章我會從InnoDB存儲空間分布,delete對性能的影響,以及優化建議方面解釋為什麼不建議delete刪除數據。

InnoDB存儲架構

從這張圖可以看到,InnoDB存儲結構主要包括兩部分:邏輯存儲結構和物理存儲結構。

邏輯上是由表空間tablespace —> 段segment或者inode —> 區Extent ——>數據頁Page構成,Innodb邏輯管理單位是segment,空間分配的最小單位是extent,每個segment都會從表空間FREE_PAGE中分配32個page,當這32個page不夠用時,會按照以下原則進行擴展:如果當前小於1個extent,則擴展到1個extent;當表空間小於32MB時,每次擴展一個extent;表空間大於32MB,每次擴展4個extent。

物理上主要由系統用戶數據文件,日誌文件組成,數據文件主要存儲MySQL字典數據和用戶數據,日誌文件記錄的是data page的變更記錄,用於MySQL Crash時的恢復。

Innodb表空間

InnoDB存儲包括三類表空間:系統表空間,用戶表空間,Undo表空間。

**系統表空間:**主要存儲MySQL內部的數據字典數據,如information_schema下的數據。

**用戶表空間:**當開啟innodb_file_per_table=1時,數據表從系統表空間獨立出來存儲在以table_name.ibd命令的數據文件中,結構信息存儲在table_name.frm文件中。

**Undo表空間:**存儲Undo信息,如快照一致讀和flashback都是利用undo信息。

從MySQL 8.0開始允許用戶自定義表空間,具體語法如下:

這樣的好處是可以做到數據的冷熱分離,分別用HDD和SSD來存儲,既能實現數據的高效訪問,又能節約成本,比如可以添加兩塊500G硬碟,經過創建卷組vg,劃分邏輯卷lv,創建數據目錄並mount相應的lv,假設劃分的兩個目錄分別是/hot_data 和 /cold_data。

這樣就可以將核心的業務表如用戶表,訂單表存儲在高性能SSD盤上,一些日誌,流水錶存儲在普通的HDD上,主要的操作步驟如下:

Inndob存儲分布

創建空表查看空間變化

設置參數innodb_file_per_table=1時,創建表時會自動創建一個segment,同時分配一個extent,包含32個data page的來存儲數據,這樣創建的空表默認大小就是96KB,extent使用完之後會申請64個連接頁,這樣對於一些小表,或者undo segment,可以在開始時申請較少的空間,節省磁碟容量的開銷。

插入數據後的空間變化

delete數據後的空間變化

MySQL內部不會真正刪除空間,而且做標記刪除,即將delflag:N修改為delflag:Y,commit之後會會被purge進入刪除鍊表,如果下一次insert更大的記錄,delete之後的空間不會被重用,如果插入的記錄小於等於delete的記錄空會被重用,這塊內容可以通過知數堂的innblock工具進行分析。

Innodb中的碎片

碎片的產生

我們知道數據存儲在文件系統上的,總是不能100%利用分配給它的物理空間,刪除數據會在頁面上留下一些」空洞」,或者隨機寫入(聚集索引非線性增加)會導致頁分裂,頁分裂導致頁面的利用空間少於50%,另外對表進行增刪改會引起對應的二級索引值的隨機的增刪改,也會導致索引結構中的數據頁面上留下一些"空洞",雖然這些空洞有可能會被重複利用,但終究會導致部分物理空間未被使用,也就是碎片。

同時,即便是設置了填充因子為100%,Innodb也會主動留下page頁面1/16的空間作為預留使用(An innodb_fill_factor setting of 100 leaves 1/16 of the space in clustered index pages free for future index growth)防止update帶來的行溢出。

其中data_free是分配了未使用的字節數,並不能說明完全是碎片空間。

碎片的回收

對於InnoDB的表,可以通過以下命令來回收碎片,釋放空間,這個是隨機讀IO操作,會比較耗時,也會阻塞表上正常的DML運行,同時需要佔用額外更多的磁碟空間,對於RDS來說,可能會導致磁碟空間瞬間爆滿,實例瞬間被鎖定,應用無法做DML操作,所以禁止在線上環境去執行。

delete對SQL的影響

未刪除前的SQL執行情況

刪除後的SQL執行情況

結果統計分析

這也說明對普通的大表,想要通過delete數據來對表進行瘦身是不現實的,所以在任何時候不要用delete去刪除數據,應該使用優雅的標記刪除。

delete優化建議

控制業務帳號權限

對於一個大的系統來說,需要根據業務特點去拆分子系統,每個子系統可以看做是一個service,例如美團APP,上面有很多服務,核心的服務有用戶服務user-service,搜索服務search-service,商品product-service,位置服務location-service,價格服務price-service等。每個服務對應一個資料庫,為該資料庫創建單獨帳號,同時只授予DML權限且沒有delete權限,同時禁止跨庫訪問。

delete改為標記刪除

在MySQL資料庫建模規範中有4個公共欄位,基本上每個表必須有的,同時在create_time列要創建索引,有兩方面的好處:

一些查詢業務場景都會有一個默認的時間段,比如7天或者一個月,都是通過create_time去過濾,走索引掃描更快。一些核心的業務表需要以T +1的方式抽取數據倉庫中,比如每天晚上00:30抽取前一天的數據,都是通過create_time過濾的。

數據歸檔方式

通用數據歸檔方法

優化後的歸檔方式

這樣原表和歸檔表都是按月的分區表,只需要創建一個中間普通表,在業務低峰期做兩次分區交換,既可以刪除無效數據,又能回收空,而且沒有空間碎片,不會影響表上的索引及SQL的執行計劃。

總結

通過從InnoDB存儲空間分布,delete對性能的影響可以看到,delete物理刪除既不能釋放磁碟空間,而且會產生大量的碎片,導致索引頻繁分裂,影響SQL執行計劃的穩定性;

同時在碎片回收時,會耗用大量的CPU,磁碟空間,影響表上正常的DML操作。

在業務代碼層面,應該做邏輯標記刪除,避免物理刪除;為了實現數據歸檔需求,可以用採用MySQL分區表特性來實現,都是DDL操作,沒有碎片產生。

另外一個比較好的方案採用Clickhouse,對有生命周期的數據表可以使用Clickhouse存儲,利用其TTL特性實現無效數據自動清理。

相關焦點

  • 如何向mysql導入數據
    一、導入.sql文件1.mysql命令導入數據基本語法:mysql -h伺服器地址 -u用戶名 -p 資料庫名< 要導入的資料庫文件路徑例:導入G:若指定replace,刪除原有行,插入新行。若指定ignore,則保留原有行,跳過相同值的新行。若不指定,則找到重複鍵值時,出現一個錯誤,且文本文件的餘下部分將被忽略。fields terminated by:指定欄位間的分隔符,默認'\t'。lines terminated by:指定每行結尾的字符,默認'\n'。
  • mysql 矩陣類型專題及常見問題 - CSDN
    刪除數據表刪除數據刪除數據mysql語句:delete from table where 條件實現:import pymysqldb = pymysql.connect(host='localhost',user='root',password='123456
  • 一千行MySQL學習筆記
    可以不指定主表記錄更改或更新時的動作,那麼此時主表的操作被拒絕。    如果指定了 on update 或 on delete:在刪除或更新時,有如下幾個操作可以選擇:    1. cascade,級聯操作。主表數據被更新(主鍵值更新),從表也被更新(外鍵值更新)。主表記錄被刪除,從表相關記錄也被刪除。    2. set null,設置為null。
  • 數據自動化第三篇(上):三種方法實現數據入庫功能
    上一節我們已經實現了資料庫連接池,視頻版和文字版的內容都已更新,這節我們將要連接資料庫,實現數據的增刪改查。但還有一點需要注意,在數據自動化當中很少有刪除操作,一般只涉及更新(新增)和查詢,所以,外面我們主要實現這兩個方法。
  • Python連接MySQL資料庫方法介紹(超詳細!手把手項目案例操作)
    for i in data[:2]: # 列印輸出前2條數據print (i)cur.close() # 關閉遊標conn.close() # 關閉連接上述代碼中,實現了通過Python連接MySQL查詢所有的數據,並輸出前2條數據的功能。
  • MySQL 5.7和MySQL 8.0的4個細節差異
    細節1:比如我們在MySQL 5.7版本中全面推行GTID,所以之前的create table xxx as select * from xx的使用模式就不奏效了,進而我們建議使用:create table xxx like xxxxx;insert into xxx select * from xxxxx;這種使用模式,而MySQL8.0
  • mysql 版本號解釋_mysql workbench查詢mysql版本號 - CSDN
    對於哈希索引來說,底層的數據結構就是哈希表,因此在絕大多數需求為單條記錄查詢的時候,可以選擇哈希索引,查詢性能最快;其餘大部分場景,建議選擇BTree索引。1)B樹索引mysql通過存儲引擎取數據,基本上90%的人用的就是InnoDB了,按照實現方式分,InnoDB的索引類型目前只有兩種:BTREE(B樹)索引和HASH索引。
  • python3.8操作(插入,刪除)mysql/MariaDB資料庫
    = mysql.connector.connect(host="localhost", # 資料庫主機地址user="root", # 資料庫用戶名passwd="root", # 資料庫密碼database="test1" #資料庫名)mycursor = mydb.cursor()a=0while True:time.sleep(1)try:# 查詢資料庫的表格table1mycursor.execute
  • MySQL 數據校驗工具-愛可生|mysql|perl|伺服器|node01_網易訂閱
    概述  pt-table-checksum 是 Percona-Toolkit 的組件之一,用於檢測 MySQL 主、從庫的數據是否一致。其原理是在主庫執行基於 statement 的 SQL 語句來生成主庫數據塊的checksum,把相同的 SQL 語句傳遞到從庫執行,並在從庫上計算相同數據塊的 checksum,最後,比較主從庫上相同數據塊的 checksum 值,由此判斷主從數據是否一致。它能在非常大的表上工作的一個原因是,它把每個表分成行塊,並檢查每個塊與單個替換。選擇查詢。它改變塊的大小,使校驗和查詢在所需的時間內運行。
  • mysql查詢前一周的數據_mysql查詢當天的數據 - CSDN
    mysql 昨天 一周前 一月前 一年前的數據 這裡主要用到了DATE_SUB,參考如下代碼如下:SELECT * FROM
  • 如何把Access的數據導入到Mysql中
    在Mysql資料庫中,一般有兩種方法來處理數據的導出:1. 使用select * from table_name into outfile 「file_name」; 2. 使用mysqldump實用程序 下面我們來舉例說明: 假設我們的資料庫中有一個庫為samp_db,一個表為samp_table。現在要把samp_table的數據導出。
  • MySQL8.0窗口函數做數據排名統計詳細教程
    MySQL8.0新增了窗口函數,大大的方便了做數據排名統計的人,很多朋友還不清楚怎麼用MySQL8.0做數據統計排名,下面就來為大家分享一篇心得文章。
  • MySQL如何實時同步數據到ES?試試這款阿里開源的神器
    canal的各個組件的用途各不相同,下面分別介紹下: canal-server(canal-deploy):可以直接監聽MySQL的binlog,把自己偽裝成MySQL的從庫,只負責接收數據,並不做處理。 canal-adapter:相當於canal的客戶端,會從canal-server中獲取數據,然後對數據進行同步,可以同步到MySQL、Elasticsearch和HBase等存儲中去。
  • MYSQL8.0 hash join中的笛卡爾積關聯的現象解析
    insert into t3 values('a5097','name5097','addr5097'); insert into t3 values('a5098','name5098','addr5098'); insert into t3 values('a5099','name5099','addr5099');
  • 高性能Mysql主從架構的複製原理及配置詳解
    如果因為mysql版本新舊密碼算法不同,可以設置:拷貝數據:(假如是你完全新安裝mysql主從伺服器,這個一步就不需要。至少在目前來看,MySQL是做不到的,以後是否會支持就不清楚了。MySQL不支持一個Slave節點從多個Master節點來進行複製的架構,主要是為了避免衝突的問題,防止多個數據源之間的數據出現衝突,而造成最後數據的不一致性。不過聽說已經有人開發了相關的patch,讓MySQL支持一個Slave節點從多個Master結點作為數據源來進行複製,這也正是MySQL開源的性質所帶來的好處。
  • 將mysql數據導入access資料庫
    mysql資料庫表sqltable 欄位id,name,sex,email access資料庫表accesstable id,name,sex,email ?
  • Mysql(Mariadb)資料庫主從複製
    設置server-id,必須唯一log-bin="/var/lib/mysql/" #設定生成的log文件名; 修改後:# systemctl restart mariadb.service2.重啟mysql,打開mysql會話,執行同步SQL語句(需要主伺服器主機名,登陸憑據,二進位文件的名稱和位置):# mysql -hlocalhost
  • MySQL資料庫常見的出錯代碼及出錯信息
    > 資料庫最新資訊 > 正文 MySQL資料庫常見的出錯代碼及出錯信息 本文介紹的MySQL資料庫的出錯代碼表,依據MySQL資料庫頭文件mysql
  • MySQL 8.0 正式版 8.0.11 發布:比 MySQL 5.7 快 2 倍
    注意:從 MySQL 5.7 升級到 MySQL 8.0 僅支持通過使用 in-place 方式進行升級,並且不支持從 MySQL 8.0 降級到 MySQL 5.7(或從某個 MySQL 8.0 版本降級到任意一個更早的 MySQL 8.0 版本)。唯一受支持的替代方案是在升級之前對數據進行備份。
  • 基於MySQL資料庫應用開發實現嵌入式數控系統的設計
    具體創建過程如下: % mysql –uroot mysql; //進入資料庫 mysql》 SET PASSWORD=PASSWORD(『lyw00001』); //為根用戶分配一個口令 mysql》 CREATE DATABASE work ; //創建work資料庫 mysql》 GRANT ALL ON work.* to lyw@localhost