目錄
InnoDB儲存架構
Innodb表空間
Inndob儲存分佈
建立空表查看空間變化
插入資料後的空間變化
delete資料後的空間變化
Innodb中的碎片
碎片的產生
碎片的回收
delete对SQL的影响
未删除前的SQL执行情况
删除后的SQL执行情况
结果统计分析
delete优化建议
控制业务账号权限
delete改为标记删除
数据归档方式
通用数据归档方法
优化后的归档方式
总结
首頁 資料庫 mysql教程 mysql刪除資料時為什麼不使用delete

mysql刪除資料時為什麼不使用delete

Jul 21, 2021 am 09:26 AM
mysql

有些表的資料量成長很快,對應SQL掃描了很多無效數據,導致SQL慢了下來,經過確認之後,這些大表都是一些流水、記錄、日誌類型數據,只需要保留1到3個月,此時需要對錶做資料清理實現瘦身。

mysql刪除資料時為什麼不使用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開始允許使用者自訂表空間,具體語法如下:

CREATE TABLESPACE tablespace_name
    ADD DATAFILE 'file_name'               #数据文件名
    USE LOGFILE GROUP logfile_group        #自定义日志文件组,一般每组2个logfile。
    [EXTENT_SIZE [=] extent_size]          #区大小
    [INITIAL_SIZE [=] initial_size]        #初始化大小 
    [AUTOEXTEND_SIZE [=] autoextend_size]  #自动扩宽尺寸
    [MAX_SIZE [=] max_size]                #单个文件最大size,最大是32G。
    [NODEGROUP [=] nodegroup_id]           #节点组
    [WAIT]
    [COMMENT [=] comment_text]
    ENGINE [=] engine_name
登入後複製

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

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

#创建热数据表空间
create tablespace tbs_data_hot add datafile '/hot_data/tbs_data_hot01.dbf' max_size 20G;
#创建核心业务表存储在热数据表空间
create table booking(id bigint not null primary key auto_increment, …… ) tablespace tbs_data_hot;
#创建冷数据表空间
create tablespace tbs_data_cold add datafile '/hot_data/tbs_data_cold01.dbf' max_size 20G;
#创建日志,流水,备份类的表存储在冷数据表空间
create table payment_log(id bigint not null primary key auto_increment, …… ) tablespace tbs_data_cold;
#可以移动表到另一个表空间
alter table payment_log tablespace tbs_data_hot;
登入後複製

Inndob儲存分佈

建立空表查看空間變化

mysql> create table user(id bigint not null primary key auto_increment, 
    -> name varchar(20) not null default '' comment '姓名', 
    -> age tinyint not null default 0 comment 'age', 
    -> gender char(1) not null default 'M'  comment '性别',
    -> phone varchar(16) not null default '' comment '手机号',
    -> create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    -> update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'
    -> ) engine = InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户信息表';
Query OK, 0 rows affected (0.26 sec)
登入後複製
# ls -lh user1.ibd 
-rw-r----- 1 mysql mysql 96K Nov  6 12:48 user.ibd
登入後複製

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

# python2 py_innodb_page_info.py -v /data2/mysql/test/user.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000000, page type <Freshly Allocated Page>
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 6:      #总共分配的页数
Freshly Allocated Page: 2     #可用的数据页
Insert Buffer Bitmap: 1       #插入缓冲页
File Space Header: 1          #文件空间头
B-tree Node: 1                #数据页
File Segment inode: 1         #文件端inonde,如果是在ibdata1.ibd上会有多个inode。
登入後複製

插入資料後的空間變化

mysql> DELIMITER $$
mysql> CREATE PROCEDURE insert_user_data(num INTEGER) 
    -> BEGIN
    ->     DECLARE v_i int unsigned DEFAULT 0;
    -> set autocommit= 0;
    -> WHILE v_i < num DO
    ->    insert into user(`name`, age, gender, phone) values (CONCAT(&#39;lyn&#39;,v_i), mod(v_i,120), &#39;M&#39;, CONCAT(&#39;152&#39;,ROUND(RAND(1)*100000000)));
    ->  SET v_i = v_i+1;
    -> END WHILE;
    -> commit;
    -> END $$
Query OK, 0 rows affected (0.01 sec)
mysql> DELIMITER ;

#插入10w数据
mysql> call insert_user_data(100000);
Query OK, 0 rows affected (6.69 sec)
登入後複製
# ls -lh user.ibd
-rw-r----- 1 mysql mysql 14M Nov 6 10:58 /data2/mysql/test/user.ibd

# python2 py_innodb_page_info.py -v /data2/mysql/test/user.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0001>   #增加了一个非叶子节点,树的高度从1变为2.
........................................................
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 896:
Freshly Allocated Page: 493
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 400
File Segment inode: 1
登入後複製

delete資料後的空間變化

mysql> select min(id),max(id),count(*) from user;
+---------+---------+----------+
| min(id) | max(id) | count(*) |
+---------+---------+----------+
|       1 |  100000 |   100000 |
+---------+---------+----------+
1 row in set (0.05 sec)
#删除50000条数据,理论上空间应该从14MB变长7MB左右。
mysql> delete from user limit 50000;
Query OK, 50000 rows affected (0.25 sec)

#数据文件大小依然是14MB,没有缩小。
# ls -lh /data2/mysql/test/user1.ibd 
-rw-r----- 1 mysql mysql 14M Nov  6 13:22 /data2/mysql/test/user.ibd

#数据页没有被回收。
# python2 py_innodb_page_info.py -v /data2/mysql/test/user.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0001>
........................................................
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 896:
Freshly Allocated Page: 493
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 400
File Segment inode: 1
#在MySQL内部是标记删除,
登入後複製
mysql> use information_schema;

Database changed
mysql> SELECT A.SPACE AS TBL_SPACEID, A.TABLE_ID, A.NAME AS TABLE_NAME, FILE_FORMAT, ROW_FORMAT, SPACE_TYPE,  B.INDEX_ID , B.NAME AS INDEX_NAME, PAGE_NO, B.TYPE AS INDEX_TYPE FROM INNODB_SYS_TABLES A LEFT JOIN INNODB_SYS_INDEXES B ON A.TABLE_ID =B.TABLE_ID WHERE A.NAME = &#39;test/user1&#39;;
+-------------+----------+------------+-------------+------------+------------+----------+------------+---------+------------+
| TBL_SPACEID | TABLE_ID | TABLE_NAME | FILE_FORMAT | ROW_FORMAT | SPACE_TYPE | INDEX_ID | INDEX_NAME | PAGE_NO | INDEX_TYPE |
+-------------+----------+------------+-------------+------------+------------+----------+------------+---------+------------+
|        1283 |     1207 | test/user | Barracuda   | Dynamic    | Single     |     2236 | PRIMARY    |       3 |          3 |
+-------------+----------+------------+-------------+------------+------------+----------+------------+---------+------------+
1 row in set (0.01 sec)

PAGE_NO = 3 标识B-tree的root page是3号页,INDEX_TYPE = 3是聚集索引。 INDEX_TYPE取值如下:
0 = nonunique secondary index; 
1 = automatically generated clustered index (GEN_CLUST_INDEX); 
2 = unique nonclustered index; 
3 = clustered index; 
32 = full-text index;
#收缩空间再后进行观察
登入後複製

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帶來的行溢位。

mysql> select table_schema,
    ->        table_name,ENGINE,
    ->        round(DATA_LENGTH/1024/1024+ INDEX_LENGTH/1024/1024) total_mb,TABLE_ROWS,
    ->        round(DATA_LENGTH/1024/1024) data_mb, round(INDEX_LENGTH/1024/1024) index_mb, round(DATA_FREE/1024/1024) free_mb, round(DATA_FREE/DATA_LENGTH*100,2) free_ratio
    -> from information_schema.TABLES where  TABLE_SCHEMA= &#39;test&#39;
    -> and TABLE_NAME= &#39;user&#39;;
+--------------+------------+--------+----------+------------+---------+----------+---------+------------+
| table_schema | table_name | ENGINE | total_mb | TABLE_ROWS | data_mb | index_mb | free_mb | free_ratio |
+--------------+------------+--------+----------+------------+---------+----------+---------+------------+
| test         | user      | InnoDB |        4 |      50000 |       4 |        0 |       6 |     149.42 |
+--------------+------------+--------+----------+------------+---------+----------+---------+------------+
1 row in set (0.00 sec)
登入後複製

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

碎片的回收

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

#执行InnoDB的碎片回收
mysql> alter table user engine=InnoDB;
Query OK, 0 rows affected (9.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

##执行完之后,数据文件大小从14MB降低到10M。
# ls -lh /data2/mysql/test/user1.ibd 
-rw-r----- 1 mysql mysql 10M Nov 6 16:18 /data2/mysql/test/user.ibd
登入後複製
mysql> select table_schema,        
    ->table_name,ENGINE,        
    ->round(DATA_LENGTH/1024/1024+ INDEX_LENGTH/1024/1024) total_mb,TABLE_ROWS,        
    ->round(DATA_LENGTH/1024/1024) data_mb, 
    ->round(INDEX_LENGTH/1024/1024) index_mb, 
    ->round(DATA_FREE/1024/1024) free_mb, 
    ->round(DATA_FREE/DATA_LENGTH*100,2) free_ratio from information_schema.TABLES where  TABLE_SCHEMA= &#39;test&#39; and TABLE_NAME= &#39;user&#39;;
+--------------+------------+--------+----------+------------+---------+----------+---------+------------+
| table_schema | table_name | ENGINE | total_mb | TABLE_ROWS | data_mb | index_mb | free_mb | free_ratio |
+--------------+------------+--------+----------+------------+---------+----------+---------+------------+
| test         | user      | InnoDB |        5 |      50000 |       5 |        0 |       2 |      44.29 |
+--------------+------------+--------+----------+------------+---------+----------+---------+------------+
1 row in set (0.00 sec)
登入後複製

delete对SQL的影响

未删除前的SQL执行情况

#插入100W数据
mysql> call insert_user_data(1000000);
Query OK, 0 rows affected (35.99 sec)

#添加相关索引
mysql> alter table user add index idx_name(name), add index idx_phone(phone);
Query OK, 0 rows affected (6.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

#表上索引统计信息
mysql> show index from user;
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| user  |          0 | PRIMARY   |            1 | id          | A         |      996757 |     NULL | NULL   |      | BTREE      |         |               |
| user  |          1 | idx_name  |            1 | name        | A         |      996757 |     NULL | NULL   |      | BTREE      |         |               |
| user  |          1 | idx_phone |            1 | phone       | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

#重置状态变量计数
mysql> flush status;
Query OK, 0 rows affected (0.00 sec)

#执行SQL语句
mysql> select id, age ,phone from user where name like &#39;lyn12%&#39;;
+--------+-----+-------------+
| id     | age | phone       |
+--------+-----+-------------+
|    124 |   3 | 15240540354 |
|   1231 |  30 | 15240540354 |
|  12301 |  60 | 15240540354 |
.............................
| 129998 |  37 | 15240540354 |
| 129999 |  38 | 15240540354 |
| 130000 |  39 | 15240540354 |
+--------+-----+-------------+
11111 rows in set (0.03 sec)

mysql> explain select id, age ,phone from user where name like &#39;lyn12%&#39;;
+----+-------------+-------+-------+---------------+----------+---------+------+-------+-----------------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows  | Extra                 |
+----+-------------+-------+-------+---------------+----------+---------+------+-------+-----------------------+
|  1 | SIMPLE      | user  | range | idx_name      | idx_name | 82      | NULL | 22226 | Using index condition |
+----+-------------+-------+-------+---------------+----------+---------+------+-------+-----------------------+
1 row in set (0.00 sec)

#查看相关状态呢变量
mysql> select * from information_schema.session_status where variable_name in(&#39;Last_query_cost&#39;,&#39;Handler_read_next&#39;,&#39;Innodb_pages_read&#39;,&#39;Innodb_data_reads&#39;,&#39;Innodb_pages_read&#39;);
+-------------------+----------------+
| VARIABLE_NAME     | VARIABLE_VALUE |
+-------------------+----------------+
| HANDLER_READ_NEXT | 11111          |    #请求读的行数
| INNODB_DATA_READS | 7868409        |    #数据物理读的总数
| INNODB_PAGES_READ | 7855239        |    #逻辑读的总数
| LAST_QUERY_COST   | 10.499000      |    #SQL语句的成本COST,主要包括IO_COST和CPU_COST。
+-------------------+----------------+
4 rows in set (0.00 sec)
登入後複製

删除后的SQL执行情况

#删除50w数据
mysql> delete from user limit 500000;
Query OK, 500000 rows affected (3.70 sec)

#分析表统计信息
mysql> analyze table user;
+-----------+---------+----------+----------+
| Table     | Op      | Msg_type | Msg_text |
+-----------+---------+----------+----------+
| test.user | analyze | status   | OK       |
+-----------+---------+----------+----------+
1 row in set (0.01 sec)

#重置状态变量计数
mysql> flush status;
Query OK, 0 rows affected (0.01 sec)

mysql> select id, age ,phone from user where name like &#39;lyn12%&#39;;
Empty set (0.05 sec)

mysql> explain select id, age ,phone from user where name like &#39;lyn12%&#39;;
+----+-------------+-------+-------+---------------+----------+---------+------+-------+-----------------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows  | Extra                 |
+----+-------------+-------+-------+---------------+----------+---------+------+-------+-----------------------+
|  1 | SIMPLE      | user  | range | idx_name      | idx_name | 82      | NULL | 22226 | Using index condition |
+----+-------------+-------+-------+---------------+----------+---------+------+-------+-----------------------+
1 row in set (0.00 sec)

mysql> select * from information_schema.session_status where variable_name in(&#39;Last_query_cost&#39;,&#39;Handler_read_next&#39;,&#39;Innodb_pages_read&#39;,&#39;Innodb_data_reads&#39;,&#39;Innodb_pages_read&#39;);
+-------------------+----------------+
| VARIABLE_NAME     | VARIABLE_VALUE |
+-------------------+----------------+
| HANDLER_READ_NEXT | 0              |
| INNODB_DATA_READS | 7868409        |
| INNODB_PAGES_READ | 7855239        |
| LAST_QUERY_COST   | 10.499000      |
+-------------------+----------------+
4 rows in set (0.00 sec)
登入後複製

结果统计分析

操作COST物理读次数逻辑读次数扫描行数返回行数执行时间
初始化插入100W10.49900078684097855239222261111130ms
100W随机删除50W10.4990007868409785523922226050ms

这也说明对普通的大表,想要通过delete数据来对表进行瘦身是不现实的,所以在任何时候不要用delete去删除数据,应该使用优雅的标记删除。

delete优化建议

控制业务账号权限

对于一个大的系统来说,需要根据业务特点去拆分子系统,每个子系统可以看做是一个service,例如美团APP,上面有很多服务,核心的服务有用户服务user-service,搜索服务search-service,商品product-service,位置服务location-service,价格服务price-service等。每个服务对应一个数据库,为该数据库创建单独账号,同时只授予DML权限且没有delete权限,同时禁止跨库访问。

#创建用户数据库并授权
create database mt_user charset utf8mb4;
grant USAGE, SELECT, INSERT, UPDATE ON mt_user.*  to &#39;w_user&#39;@&#39;%&#39; identified by &#39;t$W*g@gaHTGi123456&#39;;
flush privileges;
登入後複製

delete改为标记删除

在MySQL数据库建模规范中有4个公共字段,基本上每个表必须有的,同时在create_time列要创建索引,有两方面的好处:

  • 一些查询业务场景都会有一个默认的时间段,比如7天或者一个月,都是通过create_time去过滤,走索引扫描更快。

  • 一些核心的业务表需要以T +1的方式抽取数据仓库中,比如每天晚上00:30抽取前一天的数据,都是通过create_time过滤的。

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT &#39;主键id&#39;,
`is_deleted` tinyint(4) NOT NULL DEFAULT &#39;0&#39; COMMENT &#39;是否逻辑删除:0:未删除,1:已删除&#39;,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT &#39;创建时间&#39;,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT &#39;修改时间&#39;

#有了删除标记,业务接口的delete操作就可以转换为update
update user set is_deleted = 1 where user_id = 1213;

#查询的时候需要带上is_deleted过滤
select id, age ,phone from user where is_deleted = 0 and name like &#39;lyn12%&#39;;
登入後複製

数据归档方式

通用数据归档方法

#1. 创建归档表,一般在原表名后面添加_bak。
CREATE TABLE `ota_order_bak` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT &#39;主键&#39;,
  `order_id` varchar(255) DEFAULT NULL COMMENT &#39;订单id&#39;,
  `ota_id` varchar(255) DEFAULT NULL COMMENT &#39;ota&#39;,
  `check_in_date` varchar(255) DEFAULT NULL COMMENT &#39;入住日期&#39;,
  `check_out_date` varchar(255) DEFAULT NULL COMMENT &#39;离店日期&#39;,
  `hotel_id` varchar(255) DEFAULT NULL COMMENT &#39;酒店ID&#39;,
  `guest_name` varchar(255) DEFAULT NULL COMMENT &#39;顾客&#39;,
  `purcharse_time` timestamp NULL DEFAULT NULL COMMENT &#39;购买时间&#39;,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `create_user` varchar(255) DEFAULT NULL,
  `update_user` varchar(255) DEFAULT NULL,
  `status` int(4) DEFAULT &#39;1&#39; COMMENT &#39;状态 : 1 正常 , 0 删除&#39;,
  `hotel_name` varchar(255) DEFAULT NULL,
  `price` decimal(10,0) DEFAULT NULL,
  `remark` longtext,
  PRIMARY KEY (`id`),
  KEY `IDX_order_id` (`order_id`) USING BTREE,
  KEY `hotel_name` (`hotel_name`) USING BTREE,
  KEY `ota_id` (`ota_id`) USING BTREE,
  KEY `IDX_purcharse_time` (`purcharse_time`) USING BTREE,
  KEY `IDX_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY RANGE (to_days(create_time)) ( 
PARTITION p201808 VALUES LESS THAN (to_days(&#39;2018-09-01&#39;)), 
PARTITION p201809 VALUES LESS THAN (to_days(&#39;2018-10-01&#39;)), 
PARTITION p201810 VALUES LESS THAN (to_days(&#39;2018-11-01&#39;)), 
PARTITION p201811 VALUES LESS THAN (to_days(&#39;2018-12-01&#39;)), 
PARTITION p201812 VALUES LESS THAN (to_days(&#39;2019-01-01&#39;)), 
PARTITION p201901 VALUES LESS THAN (to_days(&#39;2019-02-01&#39;)), 
PARTITION p201902 VALUES LESS THAN (to_days(&#39;2019-03-01&#39;)), 
PARTITION p201903 VALUES LESS THAN (to_days(&#39;2019-04-01&#39;)), 
PARTITION p201904 VALUES LESS THAN (to_days(&#39;2019-05-01&#39;)), 
PARTITION p201905 VALUES LESS THAN (to_days(&#39;2019-06-01&#39;)), 
PARTITION p201906 VALUES LESS THAN (to_days(&#39;2019-07-01&#39;)), 
PARTITION p201907 VALUES LESS THAN (to_days(&#39;2019-08-01&#39;)), 
PARTITION p201908 VALUES LESS THAN (to_days(&#39;2019-09-01&#39;)), 
PARTITION p201909 VALUES LESS THAN (to_days(&#39;2019-10-01&#39;)), 
PARTITION p201910 VALUES LESS THAN (to_days(&#39;2019-11-01&#39;)), 
PARTITION p201911 VALUES LESS THAN (to_days(&#39;2019-12-01&#39;)), 
PARTITION p201912 VALUES LESS THAN (to_days(&#39;2020-01-01&#39;)));

#2. 插入原表中无效的数据(需要跟开发同学确认数据保留范围)
create table tbl_p201808 as select * from ota_order where create_time between &#39;2018-08-01 00:00:00&#39; and &#39;2018-08-31 23:59:59&#39;;

#3. 跟归档表分区做分区交换
alter table ota_order_bak exchange partition p201808 with table tbl_p201808; 

#4. 删除原表中已经规范的数据
delete from ota_order where create_time between &#39;2018-08-01 00:00:00&#39; and &#39;2018-08-31 23:59:59&#39; limit 3000;
登入後複製

优化后的归档方式

#1. 创建中间表
CREATE TABLE `ota_order_2020` (........) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY RANGE (to_days(create_time)) ( 
PARTITION p201808 VALUES LESS THAN (to_days(&#39;2018-09-01&#39;)), 
PARTITION p201809 VALUES LESS THAN (to_days(&#39;2018-10-01&#39;)), 
PARTITION p201810 VALUES LESS THAN (to_days(&#39;2018-11-01&#39;)), 
PARTITION p201811 VALUES LESS THAN (to_days(&#39;2018-12-01&#39;)), 
PARTITION p201812 VALUES LESS THAN (to_days(&#39;2019-01-01&#39;)), 
PARTITION p201901 VALUES LESS THAN (to_days(&#39;2019-02-01&#39;)), 
PARTITION p201902 VALUES LESS THAN (to_days(&#39;2019-03-01&#39;)), 
PARTITION p201903 VALUES LESS THAN (to_days(&#39;2019-04-01&#39;)), 
PARTITION p201904 VALUES LESS THAN (to_days(&#39;2019-05-01&#39;)), 
PARTITION p201905 VALUES LESS THAN (to_days(&#39;2019-06-01&#39;)), 
PARTITION p201906 VALUES LESS THAN (to_days(&#39;2019-07-01&#39;)), 
PARTITION p201907 VALUES LESS THAN (to_days(&#39;2019-08-01&#39;)), 
PARTITION p201908 VALUES LESS THAN (to_days(&#39;2019-09-01&#39;)), 
PARTITION p201909 VALUES LESS THAN (to_days(&#39;2019-10-01&#39;)), 
PARTITION p201910 VALUES LESS THAN (to_days(&#39;2019-11-01&#39;)), 
PARTITION p201911 VALUES LESS THAN (to_days(&#39;2019-12-01&#39;)), 
PARTITION p201912 VALUES LESS THAN (to_days(&#39;2020-01-01&#39;)));

#2. 插入原表中有效的数据,如果数据量在100W左右可以在业务低峰期直接插入,如果比较大,建议采用dataX来做,可以控制频率和大小,之前我这边用Go封装了dataX可以实现自动生成json文件,自定义大小去执行。
insert into ota_order_2020 select * from ota_order where create_time between &#39;2020-08-01 00:00:00&#39; and &#39;2020-08-31 23:59:59&#39;;

#3. 表重命名
alter table ota_order rename to ota_order_bak;  
alter table ota_order_2020 rename to ota_order;
#4. 插入差异数据
insert into ota_order select * from ota_order_bak a where not exists (select 1 from ota_order b where a.id = b.id);
#5. ota_order_bak改造成分区表,如果表比较大不建议直接改造,可以先创建好分区表,通过dataX把导入进去即可。

#6. 后续的归档方法
#创建中间普遍表
create table ota_order_mid like ota_order;
#交换原表无效数据分区到普通表
alter table ota_order exchange partition p201808 with table ota_order_mid; 
##交换普通表数据到归档表的相应分区
alter table ota_order_bak exchange partition p201808 with table ota_order_mid;
登入後複製

这样原表和归档表都是按月的分区表,只需要创建一个中间普通表,在业务低峰期做两次分区交换,既可以删除无效数据,又能回收空,而且没有空间碎片,不会影响表上的索引及SQL的执行计划。

总结

通过从InnoDB存储空间分布,delete对性能的影响可以看到,delete物理删除既不能释放磁盘空间,而且会产生大量的碎片,导致索引频繁分裂,影响SQL执行计划的稳定性;

同时在碎片回收时,会耗用大量的CPU,磁盘空间,影响表上正常的DML操作。

在业务代码层面,应该做逻辑标记删除,避免物理删除;为了实现数据归档需求,可以用采用MySQL分区表特性来实现,都是DDL操作,没有碎片产生。

另外一个比较好的方案采用Clickhouse,对有生命周期的数据表可以使用Clickhouse存储,利用其TTL特性实现无效数据自动清理。

相关推荐:《mysql教程

以上是mysql刪除資料時為什麼不使用delete的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

MySQL的角色:Web應用程序中的數據庫 MySQL的角色:Web應用程序中的數據庫 Apr 17, 2025 am 12:23 AM

MySQL在Web應用中的主要作用是存儲和管理數據。 1.MySQL高效處理用戶信息、產品目錄和交易記錄等數據。 2.通過SQL查詢,開發者能從數據庫提取信息生成動態內容。 3.MySQL基於客戶端-服務器模型工作,確保查詢速度可接受。

docker怎麼啟動mysql docker怎麼啟動mysql Apr 15, 2025 pm 12:09 PM

在 Docker 中啟動 MySQL 的過程包含以下步驟:拉取 MySQL 鏡像創建並啟動容器,設置根用戶密碼並映射端口驗證連接創建數據庫和用戶授予對數據庫的所有權限

laravel入門實例 laravel入門實例 Apr 18, 2025 pm 12:45 PM

Laravel 是一款 PHP 框架,用於輕鬆構建 Web 應用程序。它提供一系列強大的功能,包括:安裝: 使用 Composer 全局安裝 Laravel CLI,並在項目目錄中創建應用程序。路由: 在 routes/web.php 中定義 URL 和處理函數之間的關係。視圖: 在 resources/views 中創建視圖以呈現應用程序的界面。數據庫集成: 提供與 MySQL 等數據庫的開箱即用集成,並使用遷移來創建和修改表。模型和控制器: 模型表示數據庫實體,控制器處理 HTTP 請求。

解決數據庫連接問題:使用minii/db庫的實際案例 解決數據庫連接問題:使用minii/db庫的實際案例 Apr 18, 2025 am 07:09 AM

在開發一個小型應用時,我遇到了一個棘手的問題:需要快速集成一個輕量級的數據庫操作庫。嘗試了多個庫後,我發現它們要么功能過多,要么兼容性不佳。最終,我找到了minii/db,這是一個基於Yii2的簡化版本,完美地解決了我的問題。

centos7如何安裝mysql centos7如何安裝mysql Apr 14, 2025 pm 08:30 PM

優雅安裝 MySQL 的關鍵在於添加 MySQL 官方倉庫。具體步驟如下:下載 MySQL 官方 GPG 密鑰,防止釣魚攻擊。添加 MySQL 倉庫文件:rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm更新 yum 倉庫緩存:yum update安裝 MySQL:yum install mysql-server啟動 MySQL 服務:systemctl start mysqld設置開機自啟動

centos安裝mysql centos安裝mysql Apr 14, 2025 pm 08:09 PM

在 CentOS 上安裝 MySQL 涉及以下步驟:添加合適的 MySQL yum 源。執行 yum install mysql-server 命令以安裝 MySQL 服務器。使用 mysql_secure_installation 命令進行安全設置,例如設置 root 用戶密碼。根據需要自定義 MySQL 配置文件。調整 MySQL 參數和優化數據庫以提升性能。

laravel框架安裝方法 laravel框架安裝方法 Apr 18, 2025 pm 12:54 PM

文章摘要:本文提供了詳細分步說明,指導讀者如何輕鬆安裝 Laravel 框架。 Laravel 是一個功能強大的 PHP 框架,它 упростил 和加快了 web 應用程序的開發過程。本教程涵蓋了從系統要求到配置數據庫和設置路由等各個方面的安裝過程。通過遵循這些步驟,讀者可以快速高效地為他們的 Laravel 項目打下堅實的基礎。

MySQL與其他編程語言:一種比較 MySQL與其他編程語言:一種比較 Apr 19, 2025 am 12:22 AM

MySQL与其他编程语言相比,主要用于存储和管理数据,而其他语言如Python、Java、C 则用于逻辑处理和应用开发。MySQL以其高性能、可扩展性和跨平台支持著称,适合数据管理需求,而其他语言在各自领域如数据分析、企业应用和系统编程中各有优势。

See all articles