MySQL數(shù)據(jù)庫實現(xiàn)批量表分區(qū)完整示例
對于單表大數(shù)據(jù)量大的問題,如果數(shù)據(jù)支持分片,使用表分區(qū)是個不錯的選擇,那么MySQL是如何實現(xiàn)表分區(qū)的?
一、表分區(qū)條件
1.數(shù)據(jù)庫存儲引擎支持:InnoDB 和 MyISAM引擎
2.數(shù)據(jù)庫版本支持:MySQL 5.1以后(版本不同,具體的特性支持可能會有所不同)
3.數(shù)據(jù)必須有一個或多個分區(qū)鍵:作為分區(qū)的鍵(字段)必須是主鍵的一部分(聯(lián)合主鍵)
4.分區(qū)定義:每個分區(qū)必須明確地定義數(shù)據(jù)范圍
5.分區(qū)維護(hù):隨著時間推移,可能需要添加新的分區(qū)或刪除舊的分區(qū),以保持?jǐn)?shù)據(jù)庫的性能和結(jié)構(gòu)
二、常規(guī)表和分區(qū)表的區(qū)別
常規(guī)表和分區(qū)表對比
常規(guī)表 | 分區(qū)表 | |
---|---|---|
數(shù)據(jù)結(jié)構(gòu) | 所有數(shù)據(jù)存儲在單一數(shù)據(jù)文件 | 數(shù)據(jù)被邏輯上分成多個部分,可能存放在多個文件甚至多個磁盤 |
查詢優(yōu)化 | 查詢時默認(rèn)掃描整表數(shù)據(jù) | 只訪問相關(guān)分區(qū)數(shù)據(jù) |
I/O操作 | 添加、刪除或修改行操作直接作用于整表 | 只對單個分區(qū)操作,不影響其他分區(qū)數(shù)據(jù) |
備份恢復(fù) | 通常備份整表數(shù)據(jù) | 可以單獨備份或恢復(fù)特定分區(qū) |
存儲管理 | 所有數(shù)據(jù)集中存儲 | 數(shù)據(jù)分散到多個分區(qū) |
擴(kuò)展性 | 隨著數(shù)據(jù)量增長,可能會遇到性能瓶頸 | 更容易水平擴(kuò)展,可以通過增加新分區(qū)來處理更大的數(shù)據(jù)集,而不需要改變應(yīng)用程序邏輯 |
限值和復(fù)雜性 | 相對簡單,沒有特殊的創(chuàng)建或維護(hù)要求 | 設(shè)計和實現(xiàn)更加復(fù)雜,需要考慮如何正確地設(shè)置分區(qū)策略以滿足業(yè)務(wù)需求 |
從上面看分區(qū)表是否有很大的優(yōu)勢?但是同樣分區(qū)表也存在一些限值:
分區(qū)表的限制
常規(guī)表 | 分區(qū)表 | |
---|---|---|
外鍵約束 | √ | × |
全文索引 | √ | ×(5.6以前版本) ?(5.6以后版本) |
臨時表 | √ | × |
列修改 | √ | × |
特定的ALTER TABLE語句 | √ | ×(修改主鍵、唯一鍵等) |
性能影響 | 數(shù)據(jù)量影響 | 添加、刪除、合并表分區(qū),可能會導(dǎo)致鎖表從而影響性能 |
備份和恢復(fù)工具支持 | 通常工具都支持 | 不是所有的備份和恢復(fù)工具都完全支持分區(qū)表的所有特性 |
主備服務(wù)器數(shù)據(jù)復(fù)制 | 無特殊要求 | 必須保證分區(qū)規(guī)則一致性,任何不匹配都可能導(dǎo)致復(fù)制失敗或數(shù)據(jù)不一致 |
分區(qū)類型限制 | 無 | 存儲引擎可能限制分區(qū)類型 |
查詢優(yōu)化器的行為 | 簡單索引 | 表分區(qū)+索引,特情情況的復(fù)雜查詢可能會有表分區(qū)裁剪失效問題 |
三、表分區(qū)的創(chuàng)建
表分區(qū)創(chuàng)建關(guān)鍵的三個點:創(chuàng)建表、設(shè)置分區(qū)鍵、設(shè)置分片策略
示例:
CREATE TABLE sales ( id INT NOT NULL, sale_date DATE NOT NULL, amount DECIMAL(10,2) ) PARTITION BY RANGE (YEAR(sale_date)) ( PARTITION p0 VALUES LESS THAN (2020), PARTITION p1 VALUES LESS THAN (2021), PARTITION p2 VALUES LESS THAN (2022), PARTITION p3 VALUES LESS THAN MAXVALUE );
創(chuàng)建表名:sales
分區(qū)鍵:sale_date字段的year()結(jié)果,即sale_date字段的年份
分片策略:p0分區(qū)存儲小于2020年數(shù)據(jù)、 p1分區(qū)存儲小于2021年數(shù)據(jù)、p2分區(qū)存儲小于2022年數(shù)據(jù)、p3分區(qū)存儲其他年份數(shù)據(jù)(注意:這里的數(shù)據(jù)“擋板”很重要,設(shè)置時一定要小心)
注意:這里的分片策略是“LESS THAN xxx”,表示小于后面策略的數(shù)據(jù)數(shù)據(jù),如上面就是小于指定年份的數(shù)據(jù)歸屬于這個分區(qū),因此上面用“數(shù)據(jù)擋板”這個詞
四、將既有表轉(zhuǎn)換分區(qū)表腳本
因為表的創(chuàng)建結(jié)構(gòu)不同,因此既有表不能直接轉(zhuǎn)換為分區(qū)表,要實現(xiàn)既有表轉(zhuǎn)換為分區(qū)表,需要經(jīng)過以下幾步:
1.根據(jù)既有表創(chuàng)建同字段結(jié)構(gòu)的新分區(qū)表、定義好相關(guān)分區(qū)策略
2.遷移數(shù)據(jù)到分區(qū)表
3.刪除舊表、并將分區(qū)表改名為原表
具體實現(xiàn)腳本如下:
CREATE DEFINER=`root`@`%` PROCEDURE `convert_table_to_partition`(IN tbl_name VARCHAR(200),OUT out_status INT) BEGIN DECLARE done INT DEFAULT FALSE; -- 輸出狀態(tài),開始執(zhí)行狀態(tài) 100,執(zhí)行成功狀態(tài) 200,執(zhí)行失敗狀態(tài) 50 SET out_status = 100; -- 創(chuàng)建一個新的空表,不包含表分區(qū)(要轉(zhuǎn)換為分區(qū)表,必須是空表) SET @create_empty_tbl_sql = CONCAT( 'CREATE TABLE ', tbl_name, '_partitioned LIKE ', tbl_name, ';' ); PREPARE stmt FROM @create_empty_tbl_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 獲取所有唯一的 year_no 和 month_no 組合作為構(gòu)建分區(qū)定義分區(qū)鍵 SET @partition_def = ''; SET @query = CONCAT( 'SELECT GROUP_CONCAT( CONCAT("PARTITION p_", year_no, "_", LPAD(month_no, 2, "0"), " VALUES LESS THAN (", CASE WHEN month_no = 12 THEN year_no + 1 ELSE year_no END, ", ", CASE WHEN month_no = 12 THEN 1 ELSE month_no + 1 END, ")") ORDER BY year_no, month_no SEPARATOR ",\n" ) INTO @partition_def FROM ( SELECT DISTINCT year_no, month_no FROM ', tbl_name, ' ORDER BY year_no, month_no ) AS unique_years_months;' ); PREPARE stmt FROM @query; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 調(diào)試信息:輸出分區(qū)定義字符串 --SELECT tbl_name,@partition_def; -- 檢查是否有有效的分區(qū)定義 IF @partition_def IS NULL OR @partition_def = '' THEN SELECT tbl_name,'No data found for partitioning. Skipping partition creation and data migration.'; -- 空表則直接添加 p_max 分區(qū)用于捕獲未來數(shù)據(jù) SET @partition_def = '\nPARTITION p_max VALUES LESS THAN (MAXVALUE, MAXVALUE)'; ELSE -- 添加 p_max 分區(qū)用于捕獲未來數(shù)據(jù) SET @partition_def = CONCAT(@partition_def, ',\nPARTITION p_max VALUES LESS THAN (MAXVALUE, MAXVALUE)'); END IF; -- 使用 ALTER TABLE 添加分區(qū)定義 SET @add_partitions_sql = CONCAT( 'ALTER TABLE ', tbl_name, '_partitioned PARTITION BY RANGE COLUMNS(year_no, month_no) (', @partition_def, ');' ); -- 調(diào)試信息:輸出添加分區(qū)的 SQL 語句 --SELECT tbl_name,@add_partitions_sql; PREPARE stmt FROM @add_partitions_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 遷移數(shù)據(jù)到新的分區(qū)表 SET @insert_into_partitioned_sql = CONCAT( 'INSERT INTO ', tbl_name, '_partitioned SELECT * FROM ', tbl_name, ';' ); -- 調(diào)試信息:輸出插入數(shù)據(jù)的 SQL 語句 -- SELECT tbl_name,@insert_into_partitioned_sql; PREPARE stmt FROM @insert_into_partitioned_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 驗證數(shù)據(jù)遷移是否成功 SET @count_original = CONCAT('SELECT COUNT(*) INTO @count_original FROM ', tbl_name); PREPARE stmt FROM @count_original; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @count_partitioned = CONCAT('SELECT COUNT(*) INTO @count_partitioned FROM ', tbl_name, '_partitioned'); PREPARE stmt FROM @count_partitioned; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 比較原表和新分區(qū)表的數(shù)據(jù)行數(shù) -- SELECT tbl_name,@count_original, @count_partitioned; -- 如果數(shù)據(jù)遷移成功,刪除舊表并重命名新表(無論是否有數(shù)據(jù),均刪除緩存表) IF @count_original = @count_partitioned THEN -- 刪除舊表 SET @drop_old_table_sql = CONCAT('DROP TABLE IF EXISTS ', tbl_name); PREPARE stmt FROM @drop_old_table_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 重命名新表為舊表名 SET @rename_tables_sql = CONCAT('RENAME TABLE ', tbl_name, '_partitioned TO ', tbl_name); PREPARE stmt FROM @rename_tables_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- SELECT tbl_name,'Table conversion and data migration completed successfully.'; SET out_status = 200; ELSE -- SELECT tbl_name,'Data migration failed, check the logs for more information.'; SET out_status = 50; END IF; END
上面腳本是一個完整的將既有表轉(zhuǎn)換為以“year_no”和“month_no”字段為分區(qū)鍵的分區(qū)表,主要有以下幾步操作:
1)以既有表為模板創(chuàng)建一個新的空表,不包含表分區(qū)(要轉(zhuǎn)換為分區(qū)表,必須是空表)
2)獲取所有唯一的 year_no 和 month_no 組合并構(gòu)建分區(qū)定義字符串(對既有數(shù)據(jù)分析需要劃分的分區(qū)策略)
3)檢查是否有效的分區(qū)定義,若無分區(qū)定義,強(qiáng)烈建議則創(chuàng)建一個默認(rèn)的分區(qū)策略p_max以存儲未來的數(shù)據(jù)
4)更新空表,添加相關(guān)的分區(qū)策略
5)遷移歷史數(shù)據(jù)到分區(qū)表
6)數(shù)據(jù)遷移校驗(驗證數(shù)據(jù)完整性)
7)刪除舊表(回收表名)
8)將新分區(qū)表改名為原表名
五、批量轉(zhuǎn)換表為分區(qū)表
批量將常規(guī)表轉(zhuǎn)換為分區(qū)表,具體腳本如下:
CREATE DEFINER=`root`@`%` PROCEDURE `tables_convert_to_partition`() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE tbl_name VARCHAR(64); DECLARE convert_status INT; DECLARE cur CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'ai_result_%'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; read_loop: LOOP FETCH cur INTO tbl_name; IF done THEN LEAVE read_loop; END IF; -- 調(diào)試信息:輸出正在轉(zhuǎn)換的表 SELECT tbl_name,'covering...'; CALL convert_table_to_partition(tbl_name,@status); SET convert_status = @status; -- 根據(jù)返回的狀態(tài)進(jìn)行相應(yīng)的處理 CASE convert_status WHEN 100 THEN -- 開始狀態(tài),可以忽略,因為這是預(yù)期的初始狀態(tài) SELECT tbl_name, 'Started conversion.'; WHEN 200 THEN -- 成功完成 SELECT tbl_name, 'Conversion and data migration completed successfully.'; WHEN 50 THEN -- 失敗 SELECT tbl_name, 'Data migration failed. Check the logs for more information.'; ELSE -- 未知狀態(tài) SELECT tbl_name, CONCAT('Unknown status: ', status); END CASE; END LOOP; CLOSE cur; END
這里是以“ai_result_”開頭的表為例,將所有相關(guān)表轉(zhuǎn)換為分區(qū)表,在執(zhí)行這個存儲過程時,操作用戶必須要有information_schema數(shù)據(jù)庫讀取權(quán)限,這樣才能查詢出相關(guān)的表名從而進(jìn)行轉(zhuǎn)換。
該腳本建議為一次性執(zhí)行腳本,避免對標(biāo)頻繁轉(zhuǎn)換,防止鎖表(因此表名前綴已固定在代碼中,需根據(jù)自身需求修改)
六、表分區(qū)維護(hù):添加表分區(qū)
表分區(qū)經(jīng)過上面的過程創(chuàng)建,理論上已經(jīng)對歷史數(shù)據(jù)進(jìn)行表分區(qū),對未來數(shù)據(jù)也能存儲到p_max分區(qū),但是p_max分區(qū)數(shù)據(jù)如果不進(jìn)行維護(hù),同樣會有數(shù)據(jù)量過大問題,因此我們需要定期切割p_max分區(qū)并增加相關(guān)表分區(qū),這個操作需要在數(shù)據(jù)進(jìn)入之前執(zhí)行,具體執(zhí)行腳本如下:
CREATE DEFINER=`root`@`%` PROCEDURE `add_monthly_partitions`(IN tbl_name VARCHAR(64), IN year_no INT, IN month_no INT,OUT out_status INT) BEGIN DECLARE done INT DEFAULT FALSE; -- 輸出狀態(tài),開始執(zhí)行狀態(tài) 100,執(zhí)行成功狀態(tài) 200,執(zhí)行失敗狀態(tài) 50 SET out_status = 100; -- 檢查待添加的分區(qū)是否已經(jīng)存在 SET @partition_exists = EXISTS (SELECT 1 FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl_name AND PARTITION_NAME = CONCAT('p_', year_no, '_', LPAD(month_no, 2, '0'))); IF @partition_exists THEN -- 如果分區(qū)已存在,直接返回消息 -- SELECT CONCAT('Partition p_', year_no, '_', LPAD(month_no, 2, '0'), ' already exists. No action taken.') AS message; SET out_status = 200; ELSE -- 檢查表中是否已經(jīng)存在 p_max 分區(qū) SET @has_p_max = EXISTS (SELECT 1 FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl_name AND PARTITION_NAME = 'p_max'); -- 構(gòu)建添加分區(qū)的 SQL 語句 IF @has_p_max THEN -- 如果存在 p_max 分區(qū),則重新組織分區(qū),將 p_max 分割成新分區(qū)和更新后的 p_max SET @reorganize_partition_sql = CONCAT( 'ALTER TABLE ', tbl_name, ' REORGANIZE PARTITION p_max INTO ( PARTITION p_', year_no, '_', LPAD(month_no, 2, '0'), ' VALUES LESS THAN (', CASE WHEN month_no = 12 THEN year_no + 1 ELSE year_no END, ', ', CASE WHEN month_no = 12 THEN 1 ELSE month_no + 1 END, '), PARTITION p_max VALUES LESS THAN (MAXVALUE, MAXVALUE) )' ); PREPARE stmt FROM @reorganize_partition_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- SELECT CONCAT('Partition p_', year_no, '_', LPAD(month_no, 2, '0'), ' and updated p_max added successfully.') AS message; SET out_status = 200; ELSE -- 如果不存在 p_max 分區(qū),則直接添加新分區(qū) SET @add_partition_sql = CONCAT( 'ALTER TABLE ', tbl_name, ' ADD PARTITION ( PARTITION p_', year_no, '_', LPAD(month_no, 2, '0'), ' VALUES LESS THAN (', CASE WHEN month_no = 12 THEN year_no + 1 ELSE year_no END, ', ', CASE WHEN month_no = 12 THEN 1 ELSE month_no + 1 END, ') )' ); PREPARE stmt FROM @add_partition_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- SELECT CONCAT('Partition p_', year_no, '_', LPAD(month_no, 2, '0'), ' added successfully.') AS message; SET out_status = 200; END IF; END IF; END
上面腳本的執(zhí)行過程如下:
1)檢測待添加的分區(qū)是否已存在(已存在則不添加,不存在才添加)
2)檢測表中是否存在p_max 分區(qū)(檢測待切割分區(qū),若存在則切割分區(qū),若不存在這創(chuàng)建分區(qū))
3)切割p_max分區(qū)為新分區(qū)和新的p_max分區(qū)(此處會調(diào)整p_max分區(qū)的分片策略)
七、批量維護(hù):批量添加表分區(qū)
批量給相關(guān)表添加表分區(qū),具體腳本如下:
CREATE DEFINER=`root`@`%` PROCEDURE `tables_add_monthly_partition`(IN tbl_prefix VARCHAR(64), IN year_no INT, IN month_no INT,OUT out_status INT) BEGIN DECLARE done INT DEFAULT FALSE; DECLARE tbl_name VARCHAR(64); DECLARE cur CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE CONCAT(tbl_prefix, '%'); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 輸出狀態(tài),開始執(zhí)行狀態(tài) 100,執(zhí)行成功狀態(tài) 200,執(zhí)行失敗狀態(tài) 50 SET out_status = 100; -- 打開游標(biāo) OPEN cur; read_loop: LOOP FETCH cur INTO tbl_name; IF done THEN LEAVE read_loop; END IF; -- 檢查表是否已經(jīng)是分區(qū)表 SET @is_partitioned = EXISTS (SELECT 1 FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl_name); IF NOT @is_partitioned THEN -- 如果表不是分區(qū)表,先調(diào)用 convert_table_to_partition 進(jìn)行轉(zhuǎn)換 CALL convert_table_to_partition(tbl_name,@status); -- 轉(zhuǎn)換后再次檢查是否成功轉(zhuǎn)換為分區(qū)表 SET @is_partitioned = EXISTS (SELECT 1 FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl_name); IF NOT @is_partitioned THEN -- 如果轉(zhuǎn)換失敗,跳過后續(xù)操作并輸出錯誤信息 SELECT CONCAT('Failed to convert table ', tbl_name, ' to partitioned. Skipping.') AS message; SET out_status = 50; ITERATE read_loop; END IF; END IF; -- 調(diào)用 add_monthly_partitions 為當(dāng)前表添加分區(qū) CALL add_monthly_partitions(tbl_name, year_no, month_no,@status); -- 可選:輸出操作結(jié)果(用于調(diào)試) -- SELECT CONCAT('Processed table: ', tbl_name) AS status; END LOOP; -- 關(guān)閉游標(biāo) CLOSE cur; -- 輸出完成信息 -- SELECT CONCAT('Batch partition addition completed for tables with prefix "', tbl_prefix, '".') AS message; SET out_status = 200; END
批量添加表分區(qū)需要傳入相關(guān)表前綴,如上面示例中的“ai_result_”,此腳本會將非分區(qū)表轉(zhuǎn)換為分區(qū)表,再給分區(qū)表添加相應(yīng)的表分區(qū),具體執(zhí)行過程如下:
1)獲取所有相關(guān)表
2)遍歷判斷表是否是分區(qū)表
3)非分區(qū)表被轉(zhuǎn)換為分區(qū)表
4)給分區(qū)表添加表分區(qū)策略
該腳本請慎重執(zhí)行,上面我們有常規(guī)表和分區(qū)表的對比,執(zhí)行腳本很簡單(批量自動完成),但執(zhí)行的后果請慎重考慮!
總結(jié)
到此這篇關(guān)于MySQL數(shù)據(jù)庫實現(xiàn)批量表分區(qū)的文章就介紹到這了,更多相關(guān)MySQL批量表分區(qū)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mysql修改datadir導(dǎo)致無法啟動問題解決方法
這篇文章主要介紹了Mysql修改datadir導(dǎo)致無法啟動問題解決方法,本文原因是SELINUX導(dǎo)致,用關(guān)閉SELINUX的方法解決了這個問題,需要的朋友可以參考下2015-02-02Windows MySQL修改配置文件my.ini不生效問題
在Windows Server 2019上修改MySQL 5.6的安裝目錄下my.ini文件后,需要通過修改注冊表中的ImagePath值來確保MySQL讀取新的配置文件,修改時應(yīng)確保配置文件路徑正確,并且新配置不會覆蓋原有配置,以保證修改生效2025-01-01MySQL入門(三) 數(shù)據(jù)庫表的查詢操作【重要】
本節(jié)比較重要,對數(shù)據(jù)表數(shù)據(jù)進(jìn)行查詢操作,其中可能大家不熟悉的就對于INNER JOIN(內(nèi)連接)、LEFT JOIN(左連接)、RIGHT JOIN(右連接)等一些復(fù)雜查詢。 通過本節(jié)的學(xué)習(xí),可以讓你知道這些基本的復(fù)雜查詢是怎么實現(xiàn)的,,需要的朋友可以參考下2018-07-07各個系統(tǒng)如何尋找數(shù)據(jù)庫的my.ini并進(jìn)行修改方法詳解
通過編輯my.ini文件,可以對MySQL數(shù)據(jù)庫服務(wù)器進(jìn)行各種配置,比如設(shè)置監(jiān)聽的IP地址、指定端口號、設(shè)定字符集、配置緩沖區(qū)大小等等,這篇文章主要介紹了各個系統(tǒng)如何尋找數(shù)據(jù)庫的my.ini并進(jìn)行修改的相關(guān)資料,需要的朋友可以參考下2025-04-04MySQL觸發(fā)器實現(xiàn)兩表數(shù)據(jù)同步的代碼詳解
在數(shù)據(jù)庫應(yīng)用中,我們經(jīng)常需要對數(shù)據(jù)進(jìn)行某些操作,并在操作完成后進(jìn)行相應(yīng)的處理,這時候,可以使用觸發(fā)器來實現(xiàn)這些功能,MySQL提供了強(qiáng)大的觸發(fā)器功能,本文將給大家詳細(xì)介紹MySQL觸發(fā)器實現(xiàn)兩表數(shù)據(jù)同步,需要的朋友可以參考下2023-12-12