缩小非常大表上的数据类型

ani*_*ick 2 t-sql sql-server sql-server-2012

我有一个包含超过 1.5 亿条记录的表。目前的类型是:

id (Primary Key, Bigint)
idResult (Foreign key, Bigint null)
idItem (Foreign Key, Bigint null)
Number_1 (Bigint null)
Number_2 (Bigint null)
IsActive (Bigint null)
Run Code Online (Sandbox Code Playgroud)

Number_1 和 Number_2 永远不能大于 10。IsActive 显然是一个布尔值。并且这些列在代码库中的任何其他地方都不可以为空。我还想将外键字段更改为 int,但那又是另外一回事了。这张桌子是在我开始之前几年建造的,我们正在经历一些成长的痛苦。

我正在寻找转换这些列(以及其他表上的其他几个列,尽管这是主要的违规者)并回收该磁盘空间的最佳方法。我已经尝试了一个直接的Alter Table,但是,有点出乎意料,只是没有奏效。我不记得具体的错误,但我相信它与相关表格的大小有关。

现在我正在考虑手动删除并重新创建表,但我正在努力找出更好的方法来做到这一点,而不是使用 a Select TOP 10000 * FROM dbo.TABLENAME WHERE id > 0,并且简单地多次增加 where 子句。

我看过了Switch To,但这要求目标表的数据类型与源表匹配,这就是我要修复的问题!

有什么建议?我是不是看错了?

Sol*_*zky 5

首先,谢谢你这样做。这是一场如此明显的胜利,以至于很多人都看不到它有多大价值,但这将是非常值得的:)。让世界变得更加理智。

关于IsActive成为布尔值。我的猜测是你正在考虑把它变成一个BIT领域。这可能是要走的路,但有时最好选择,TINYINT因为有可能将含义扩展到 2 个以上的状态。在这种情况下,它真的变得更多了StatusID。通常情况下,事情一开始就简单地为Active / Inactive,但后来可能是Deleted和/或其他。从大小的角度来看,TINYINT始终为 1 个字节。另一方面,最多 8 个字段BIT是 1 个字节。意思是,一个字段是 1 个字节,2 个字段也是一个字节,依此类推,最多 8 个BITBITBITBIT字段存储在单个字节中。那么,有没有节省空间选择BITTINYINT当表只有1BIT场。只是需要考虑的事情。

如您所见,对大表执行 ALTER TABLE 有点多。一种选择,虽然不是一个很好的选择,是添加一个NOT NULL字段 -- Number_1new-- 带有一个DEFAULT值(由于默认值,这将是即时的,至少从 SQL 2012 开始),它们都不会自然具有(例如 255),然后在循环中缓慢迁移值,如下所示:

UPDATE TOP (5000) tab
SET tab.Number_1new = tab.Number_1
FROM [table] tab
WHERE tab.Number_1new = 255;
Run Code Online (Sandbox Code Playgroud)

完成后,请执行以下操作:

sp_rename 'table.Number_1', 'Number_1old', 'COLUMN';
sp_rename 'table.Number_1new', 'Number_1', 'COLUMN';
Run Code Online (Sandbox Code Playgroud)

当然,最好将其包装在 TRANSACTION 中,并将其包装在 TRY / CATCH 中。当相关代码已经更新并且一切都已经测试并且数据看起来不错时,那么您可以删除该Number_1old列。

然而,我发现的最好方法是创建一个新表,慢慢地转换数据,然后同时交换表和代码。我在 SQL Server Central 上的一篇文章中详细介绍了这些步骤:在几秒钟内重构 1 亿行(或更多)表。SRSLY!(需要免费注册)。以防万一该文章出现问题,以下是基本步骤:

  1. 创建一个具有理想结构的新表——[tableNew]。如果您使用的是企业版,请考虑启用 ROW 或 PAGE 压缩,因为它们有时会有所帮助。但是请先做一些研究,因为在某些情况下它们会产生负面影响。MSDN 上有文档可以帮助您弄清楚,还有一些工具可以帮助估计潜在的节省。但即使您确实启用了压缩,我也不会认为该操作是替换您在此处执行的项目。
  2. AFTER UPDATE, DELETE在 [table] 上添加触发器以保持更改同步(但无需担心新行)
  3. 创建一个 SQL 代理作业,用于批量移动丢失的行。在执行此操作的循环中执行此操作INSERT INTO [tableNew] (Columns) SELECT TOP (n) Columns FROM [table] WHERE ?? ORDER BY ??
  4. WHERE 和 ORDER BY 子句取决于具体情况。它们应该致力于充分利用聚集索引。如果新表的聚集索引在结构上与旧表/当前表相同,那么在每个循环开始时,您可以从 [tableNew] 中获取 MAX([id]) 并使用它来获取WHERE table.[id] > @MaxIdInTableNew ORDER BY table.[id].
  5. 创建新表,在当前表上触发,并在需要进行完整切换前一周左右的 SQL 代理作业。该时间范围可能会根据您的情况而改变,但请确保给自己足够的时间。对于这项工作来说,完成迁移行并且一次只有几个涓涓细流要好得多,而不是在发布应该开始时比全套集少 100k。
  6. 如果计划是迁移其他相关表(要转换为INTs的两个 FK 的 PK 引用),那么INT现在在此处创建这些字段,并且在将这些其他表迁移到具有之前不要添加 FK INT 字段作为它们的 PK。您不想为了对 FK 字段进行更改而再次重建此表。
  7. 在切换期间(当然是在 TRY / CATCH 中):
    1. 开始传输
    2. 对两个表进行最后的行计数,以确保所有内容都已移动(可能需要在发布前对行进行健全性检查,以确保触发器按预期进行更新和删除)
    3. 将当前表重命名为“旧”
    4. 将“新”表重命名为没有“新”表
    5. 删除 SQL 代理作业(或至少禁用它)
    6. 重命名和依赖对象,例如约束等
    7. 犯罪