MySQL的optimize table是个比较特殊的语句,之前写过这篇《小白学习MySQL - InnoDB支持optimize table?》,对该语句进行了些许讲解。
碰巧看到知乎上的JokerJason老师,写了一篇《MySQL:Optimize Table》的文章,用了一些代码,测试了optimize table对锁、ibd文件等的影响,还是比较直观,可以学习了解。
简介
在 MySQL 数据库中,OPTIMIZE TABLE 是一个重要的命令,用于优化表的性能和空间利用。通过重新组织表的存储结构,去除碎片,OPTIMIZE TABLE 可以帮助提高查询性能、减少存储空间占用以及减少数据碎片。本文将深入探讨 OPTIMIZE TABLE 的原理、用法、示例以及对 .ibd 文件的影响。
什么情况下使用
使用delete语句删除数据时,delete语句只会将记录的位置或者数据页标记为"可复用",但是数据库磁盘文件的大小不会改变,即表空间不会被回收,此时使用该命令可以释放空间,压缩数据文件。
原理
执行 OPTIMIZE TABLE 命令的过程包括以下几个关键步骤:
通过这个过程,OPTIMIZE TABLE 可以帮助数据库重新组织表的存储结构,提高查询性能和减少存储空间占用。
"""
测试optimize table 是否长期锁表
"""
import pymysql
def main():
conn = pymysql.connect(host="localhost",
user="root",
password="root",
database="test_optimize")
cursor = conn.cursor()
sql_create = """
create table if not exists `person` (
`id` int primary key auto_increment,
`name` varchar(255),
`marks` smallint,
`create_at` datetime,
`update_at` datetime
) engine=InnoDB default charset=utf8;
"""
cursor.execute(sql_create)
sql_insert = """insert into `person` (`name`, `marks`, `create_at`)
values (%s, %s, now())
"""
for i in range(195854, 100000000):
insert_data = (i, 1)
cursor.execute(sql_insert, insert_data)
if i % 2 == 0:
sql_update = """update `person` set create_at = now(), update_at = now() limit 1"""
cursor.execute(sql_update)
if i%999 == 0:
print(i)
i+=1
conn.commit()
cursor.close()
conn.close()
if __name__ == '__main__':
main()
开启三个线程执行update操作,在MySQL客户端执行optimize table 命令,查看update是否有明显延时。
import concurrent
import datetime
import time
from concurrent.futures import ThreadPoolExecutor
import pymysql
def main():
conn = pymysql.connect(host="localhost",
user="root",
password="root",
database="test_optimize")
cursor = conn.cursor()
for i in range(0, 10000000):
if i % 2 == 0:
sql_update = """update `person` set create_at = now(), update_at = now() limit 1"""
cursor.execute(sql_update)
conn.commit()
print("{}, time: {}".format(i, datetime.datetime.now()))
i += 1
cursor.close()
conn.close()
if __name__ == '__main__':
# 使用ThreadPoolExecutor创建一个线程池
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务到线程池
futures = [executor.submit(main) for _ in range(3)]
# 异步执行任务,并在每个任务完成后立即处理结果
for future in concurrent.futures.as_completed(futures):
result = future.result()
print(result)
执行脚本,并在MySQL客户端执行optimize table person。
MySQL root@(none):test_optimize> optimize table person
+----------------------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+----------------------+----------+----------+-------------------------------------------------------------------+
| test_optimize.person | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| test_optimize.person | optimize | status | OK |
+----------------------+----------+----------+-------------------------------------------------------------------+
2 rows in set
Time: 8.854s
这里执行optimize table时间大概有9s,如果整个过程加锁,则update操作会有明显的延迟。这里与MySQL的版本有关系,5.6及以下版本,的确会在整个过程加锁。
更详细的信息可以通过MySQL官网查看,
https://dev.mysql.com/doc/refman/8.3/en/optimize-table.html
查看ibd文件的变化
执行前,
root@fresh-vdh510:/var/lib/mysql/test_optimize# du -sh person.ibd
49M person.ibd
执行后,
root@fresh-vdh510:/var/lib/mysql/test_optimize# du -sh person.ibd
15M person.ibd
ibd文件
具体来说,.ibd文件包含以下内容:
在InnoDB中,表可以被组织为不同的表空间类型:
管理.ibd文件的方法可能包括:
需要注意的是,在进行任何涉及.ibd文件的操作时,应当确保数据库服务已停止或者相关表处于只读状态,以防止数据不一致或损坏。同时,直接操作.ibd文件而不通过MySQL服务器API可能会导致数据丢失或不可预测的行为。
也就是说在执行optimize的时候,因为其本质是创建了临时表,所以会生成一个新的ibd文件,在临时表覆盖原表之后,原ibd文件也会被删除。