mysql isreg_`Innodb` MySQL中如何优雅的删除大表跑路
最近很想寫寫MySQL相關的內容,就從這個話題出發吧
有人說刪MySQL表誰不會
不就是
drop table TABLENAME
如果在生產環境中,你對一張TB級別的大表,敲下這行命令
那么你的主管,大主管,隔壁的大主管 就會氣勢洶洶的沖向你
其原因是因為當開始Drop表的時候,因為Innodb支持事務,為保持一致性,會維護一個全局鎖
這就導致,這個時候所有關于MySQL的操作全部堵在隊列中
如果在白天,那QPS曲線跌零可是很好看的
當然有些不辭辛苦的DBA會選擇,大晚上爬起來刪表
先說結論: 先用ln建立硬鏈接,再drop表,最后用truncate刪除索引文件
Innodb - MyISAM
目前一般MySQL引擎使用的是Innodb
其最大的特點就是支持事務,雖然Select性能比MyISAM弱一點
事務
那么什么是事務?
事務就是一件事,一堆SQL組
這些SQL要么一起完成,要么一個都不做, 它是一個不可分割的工作單位
事務是并發控制的基本單位,保證了數據的完整
事務滿足著名的ACID條件
原子性: 在學習事務時,經常有人會告訴你,事務就是一系列的操作,要么全部都執行,要都不執行,這其實就是對事務原子性的刻畫;雖然事務具有原子性,但是原子性并不是只與事務有關系,它的身影在很多地方都會出現
如果操作并不具有原子性,并且可以再分為多個操作,當這些操作出現錯誤或拋出異常時,整個操作就可能不會繼續執行下去,而已經進行的操作造成的副作用就可能造成數據更新的丟失或者錯誤
其難點在于并行事務的原子性處理
MySQL 使用回滾日志undo log實現事務的原子性
一致性: 在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞
其實除了ACID的一致性,CAP原則中也有一個一致性
CAP中的一致性指的是分布式系統中的各個節點中對于同一數據的拷貝有著相同的值
ACID中一致性指的是數據庫的規則,如果 schema 中規定了一個值必須是唯一的,那么一致的系統必須確保在所有的操作中,該值都是唯一的
隔離性: 數據庫允許多個并發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致
RAED UNCOMMITED: 使用查詢語句不會加鎖,可能會讀到未提交的行Dirty Read
READ COMMITED: 只對記錄加記錄鎖,而不會在記錄之間加間隙鎖,所以允許新的記錄插入到被鎖定記錄的附近,所以再多次使用查詢語句時,可能得到不同的結果Non-Repeatable Read
REPEATABLE READ: 多次讀取同一范圍的數據會返回第一次查詢的快照,不會返回不同的數據行,但是可能發生幻讀Phantom Read
SERIALIZABLE: InnoDB 隱式地將全部的查詢語句加上共享鎖,解決了幻讀的問題
Mysql的隔離性通過共享鎖Shared、互斥鎖Exclusive、時間戳、version等手段實現
持久性: 既然是數據庫,那么一定對數據的持久存儲有著非常強烈的需求,如果數據被寫入到數據庫中,那么數據一定能夠被安全存儲在磁盤上;而事務的持久性就體現在,一旦事務被提交,那么數據一定會被寫入到數據庫中并持久存儲起來
MySQL 使用重做日志redo log實現事務的持久性
其他區別
儲存結構
MyISAM:每個MyISAM在磁盤上存儲成三個文件。第一個文件的名字以表的名字開始,擴展名指出文件類型。.frm文件存儲表定義。數據文件的擴展名為.MYD (MYData)。索引文件的擴展名是.MYI (MYIndex)。
InnoDB:所有的表都保存在同一個數據文件中(也可能是多個文件,或者是獨立的表空間文件), InnoDB表的大小只受限于操作系統文件的大小,一般為2GB
存儲空間
MyISAM:可被壓縮,存儲空間較小。支持三種不同的存儲格式:靜態表、動態表、壓縮表
InnoDB:需要更多的內存和存儲,它會在主內存中建立其專用的緩沖池用于高速緩沖數據和索引
可移植性、備份及恢復
MyISAM:數據是以文件的形式存儲,所以在跨平臺的數據轉移中會很方便。在備份和恢復時可單獨針對某個表進行操作
InnoDB:免費的方案可以是拷貝數據文件、備份binlog,或者用 mysqldump,在數據量達到幾十G的時候就相對痛苦了
AUTO_INCREMENT
MyISAM:可以和其他字段一起建立聯合索引。引擎的自動增長列必須是索引,如果是組合索引,自動增長可以不是第一列,他可以根據前面幾列進行排序后遞增
InnoDB:InnoDB中必須包含只有該字段的索引。引擎的自動增長列必須是索引,如果是組合索引也必須是組合索引的第一列
表鎖差異
MyISAM:只支持表級鎖,用戶在操作myisam表時,select,update,delete,insert語句都會給表自動加鎖,如果加鎖以后的表滿足insert并發的情況下,可以在表的尾部插入新的數據
InnoDB:支持事務和行級鎖,是innodb的最大特色。行鎖大幅度提高了多用戶并發操作的新能。但是InnoDB的行鎖,只是在WHERE的主鍵是有效的,非主鍵的WHERE都會鎖全表的
全文索引
MyISAM:支持 FULLTEXT類型的全文索引
InnoDB:不支持FULLTEXT類型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好
表主鍵
MyISAM:允許沒有任何索引和主鍵的表存在,索引都是保存行的地址
InnoDB:如果沒有設定主鍵或者非空唯一索引,就會自動生成一個6字節的主鍵(用戶不可見),數據是主索引的一部分,附加索引保存的是主索引的值
表的具體行數
MyISAM:保存有表的總行數,如果select count(*) from table;會直接取出出該值
InnoDB:沒有保存表的總行數,如果使用select count(*) from table;就會遍歷整個表,消耗相當大,但是在加了wehre條件后,myisam和innodb處理的方式都一樣
CURD操作
MyISAM:如果執行大量的SELECT,MyISAM是更好的選擇
InnoDB:如果你的數據執行大量的INSERT或UPDATE,出于性能方面的考慮,應該使用InnoDB表。DELETE 從性能上InnoDB更優,但DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除,在innodb上如果要清空保存有大量數據的表,最好使用truncate table這個命令
外鍵
MyISAM:不支持
InnoDB:支持
開啟獨立表空間
MySQL5.6.7之后會默認開啟獨立表空間
在my.cnf中,有這么一條配置
innodb_file_per_table = 1
查看表空間狀態,用下面的命令
mysql> show variables like '%per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
如果innodb_file_per_table的value值為OFF,代表采用的是共享表空間。
如果innodb_file_per_table的value值為ON,代表采用的是獨立表空間
共享表 - 獨立表
共享表空間:某一個數據庫的所有的表數據,索引文件全部放在一個文件中,默認這個共享表空間的文件路徑在data目錄下
默認的文件名為:ibdata1(此文件,可以擴展成多個)。注意,在這種方式下,運維超級不方便。你看,所有數據都在一個文件里,要對單表維護,十分不方便。另外,你在做delete操作的時候,文件內會留下很多間隙,ibdata1文件不會自動收縮。換句話說,使用共享表空間來存儲數據,會遭遇drop table之后,空間無法釋放的問題。
獨立表空間:每一個表都以獨立方式來部署,每個表都有一個.frm表描述文件,還有一個.ibd文件。
.frm文件:保存了每個表的元數據,包括表結構的定義等,該文件與數據庫引擎無關。
.ibd文件:保存了每個表的數據和索引的文件。
注意,在這種方式下,每個表都有自已獨立的表空間,這樣運維起來方便,可以實現單表在不同數據庫之間的移動。另外,在執行drop table操作的時候,是可以自動回收表空間。在執行delete操作后,可以通過執行alter table TableName engine=innodb語句來整理碎片,回收部分表空間
硬鏈接
假設,datadir = /data/mysql/, 另外,有一個database,名為bigtest
在數據庫bigtest中,有一個表,名為TABLENAME,執行下列命令
mysql> system ls -l /data/mysql/bigtest/
-rw-r----- 1 mysql mysql 9023 8 18 11:32 TABLENAME.frm
-rw-r----- 1 mysql mysql 2356792000512 8 18 11:32 TABLENAME.ibd
現在TABLENAME.ibd文件太大,導致刪表的時候過慢
那么如何解決這個問題呢,就需要使用硬鏈接對同一文件再建立一次索引
這個時候drop掉TABLENAME.ibd文件,那就是秒級
因為FS查詢該文件還有一個索引,就不會真正的刪除這張表,而只是刪除這個索引
mysql> system ln /data/mysql/bigtest/TABLENAME.ibd /data/mysql/bigtest/TABLENAME.ibd.tmp
-rw-r----- 1 mysql mysql 9023 8 18 11:32 TABLENAME.frm
-rw-r----- 2 mysql mysql 2356792000512 8 18 11:32 TABLENAME.ibd
-rw-r----- 2 mysql mysql 2356792000512 8 18 11:32 TABLENAME.ibd.tmp
此時drop表,就會瞬間結束
mysql> drop table erp;
Query OK, 0 rows affected (1.03 sec)
truncate
這個時候已經把表刪了 MySQL里面已經沒有這張表了 但磁盤并沒有釋放
磁盤里還有那個TABLENAME.ibd.tmp大文件,于是問題轉換為如何刪除一個大文件而不引起大IO
同樣這個時候不能用rm命令,用了的話磁盤IO開銷飆高, CPU打滿,ssh都連不上了,那么恭喜你又有:tea:喝了
那用什么呢
答案是truncate(其實還有其他一些方法, 但對IO影響都比較大)
其實有兩個truncate, 一個是Linux下FS對文件操作的命令,一個是MySQL中對表操作的命令
FS的truncate
truncate 和其字面意思一致,截斷
把文件截斷成指定大小(注意: 可以是放大也可以是縮小)
附上truncate的Linux源碼,其基本思路就是三次釋放間接塊,截取inode
直接塊,就是i_zone中相應保存的就直接是inode所使用的磁盤塊
一級間接塊,顧名思義,i_zone指明的塊中存放的不是普通數據,而是塊號
因此對一級間接塊的釋放操作就是讀取一級間接塊,遍歷其中每一個塊調用free_block進行釋放
二級間接塊就是i_zone中存儲的是一級間接塊的塊號
對于二級間接塊讀取一級間接塊后就可以轉換為對一級間接塊的釋放操作
同理,三級間接塊i_zone存儲的就是二級間接塊的塊號,所以釋放三級間接塊就需要三次遞歸
truncate操作則是對inode的所有塊進行釋放,最后設置其大小為0
/*
* linux/fs/truncate.c
*
* (C) 1991 Linus Torvalds
*/
#include // 調度程序頭文件,定義了任務結構task_struct、初始任務0 的數據,
// 還有一些有關描述符參數設置和獲取的嵌入式匯編函數宏語句。
#include // 文件狀態頭文件。含有文件或文件系統狀態結構stat{}和常量。
// 釋放一次間接塊。
static void free_ind(int dev, int block) {
struct buffer_head *bh;
unsigned short *p;
int i;
if (!block) return; // 如果邏輯塊號為0,則返回。
// 讀取一次間接塊,并釋放其上表明使用的所有邏輯塊,然后釋放該一次間接塊的緩沖區。
if (bh = bread(dev, block)) {
p = (unsigned short *)bh->b_data; // 指向數據緩沖區。
for (i = 0; i < 512; i++, p++) // 每個邏輯塊上可有512 個塊號。
if (*p) free_block(dev, *p); // 釋放指定的邏輯塊。
brelse(bh); // 釋放緩沖區。
}
free_block(dev, block); // 釋放設備上的一次間接塊。
}
// 釋放二次間接塊。
static void free_dind(int dev, int block) {
struct buffer_head *bh;
unsigned short *p;
int i;
if (!block) return; // 如果邏輯塊號為0,則返回。
// 讀取二次間接塊的一級塊,并釋放其上表明使用的所有邏輯塊,然后釋放該一級塊的緩沖區。
if (bh = bread(dev, block)) {
p = (unsigned short *)bh->b_data; // 指向數據緩沖區。
for (i = 0; i < 512; i++, p++) // 每個邏輯塊上可連接512 個二級塊。
if (*p) free_ind(dev, *p); // 釋放所有一次間接塊。
brelse(bh); // 釋放緩沖區。
}
free_block(dev, block); // 最后釋放設備上的二次間接塊。
}
// 將節點對應的文件長度截為0,并釋放占用的設備空間。
void truncate(struct m_inode *inode) {
int i;
// 如果不是常規文件或者是目錄文件,則返回。
if (!(S_ISREG (inode->i_mode) || S_ISDIR (inode->i_mode)))
return;
// 釋放i 節點的7 個直接邏輯塊,并將這7 個邏輯塊項全置零。
for (i = 0; i < 7; i++)
if (inode->i_zone[i]){ // 如果塊號不為0,則釋放之。
free_block (inode->i_dev, inode->i_zone[i]);
inode->i_zone[i] = 0;
}
free_ind (inode->i_dev, inode->i_zone[7]); // 釋放一次間接塊。
free_dind (inode->i_dev, inode->i_zone[8]); // 釋放二次間接塊。
inode->i_zone[7] = inode->i_zone[8] = 0; // 邏輯塊項7、8 置零。
inode->i_size = 0; // 文件大小置零。
inode->i_dirt = 1; // 置節點已修改標志。
inode->i_mtime = inode->i_ctime = CURRENT_TIME; // 重置文件和節點修改時間為當前時間。
}
SQL的truncate
一般說Sql的truncate會把它和drop,delete放在一起對比
我們知道MySQL有一系列的日志用于保護數據
尤其是對于寫操作,除了傳統的transaction log,另外還有binlog
這一些log日志都是在操作的同時進行書寫的
delete操作時,會把每條數據標記為已刪除,不可避免的導致了操作十分耗時,且實際上空間并沒有被釋放 DML
truncate操作時,把所有數據刪除,僅把表結構記錄到transition log中,很明顯這種操作較難恢復,但耗時較少 DDL
drop表的時候,就跟直接,把表數據和表結構都刪除了 DDL
drop和truncate想要恢復也是可以的但不是通過rollback,而是通過解析binlog文件
其他方法
重定向
把空字符重定向到文件中,但IO高,io會跌零
$ > /data/mysql/bigtest/TABLENAME.ibd.tmp
:/true
把true值重定向到文件中
$ : > /data/mysql/bigtest/TABLENAME.ibd.tmp
/dev/null/dd/cp
cat /dev/null > access.log
cp /dev/null access.log
dd if=/dev/null of=access.log
echo
echo "" > access.log
echo > access.log
echo -n "" > access.log
HDFS truncate
Truncate文件截斷在HDFS上的表現其實是block的截斷。傳入目標文件,與目標保留的長度(此長度應小于文件原大小)
允許用戶移除意外寫入的數據
當寫事務發生失敗的時候,可以進行回滾,回到之前寫入成功的事務狀態
有能力移除一次失敗的寫操作而寫入的不完整的數據
提升HDFS對于其它文件系統的支持度
定位到新的截取長度所對應的塊,然后把后面多余的塊從此文件中進行移除;
找到新長度所對應的block塊之后,計算此塊內部需要移除的偏移量,然后進行刪除;
參考
總結
以上是生活随笔為你收集整理的mysql isreg_`Innodb` MySQL中如何优雅的删除大表跑路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux mysql 不稳定_linu
- 下一篇: exec不同文件l怎么汇总_ABAQUS