我有一个独特的情况,我需要快速匿名化一个列。我的意思是任何删除数据的方法,无论是 NULL'ing、消隐还是其他什么。最多可以更新 2000 万条记录,并且我正在更新的列上没有索引。
我尝试了几件事,例如:
Update TABLE Set COLUMN = NULL
Run Code Online (Sandbox Code Playgroud)
这显然是性能最差的查询。我已经修改它以从该集合中排除任何当前的 NULL 或空白,但它仍然很慢。
我试图删除并重新创建列,到目前为止是即时的。但不幸的是,由于业务原因,列顺序很重要,所以这会破坏它。(出于问题的目的,假设重新排序列是不可能的)。
最近我尝试将列类型更改为char(1),然后返回到text- 具有更好的性能。但是,在看到它通过删除并重新创建列以闪电般的速度运行之后,我很好奇是否有办法保持列顺序不变。显然,SQL Server 能够立即将大约 2000 万条记录作为 NULL 生成 - 必须有一个聪明的方法来解决这个问题吗?
首先让我们快速回顾一下 SQL Server 如何在您执行SELECT *查询时生成列的默认顺序。可能有一些边缘情况,但我相信列是按照它们的创建顺序返回的。如果删除一列,则不会使用与该列关联的序号 ID,新列也不能使用该 ID。
考虑这个示例表:
CREATE TABLE dbo.SEE_COLUMN_ORDER (
COL1 INT,
COL2 INT,
COL3 INT,
COL4 INT
);
Run Code Online (Sandbox Code Playgroud)
我们可以使用针对sys.columns 的查询来查看 column_id :
SELECT name, column_id
FROM sys.columns
WHERE object_id = OBJECT_ID('SEE_COLUMN_ORDER');
Run Code Online (Sandbox Code Playgroud)
初步结果:
????????????????????
? name ? column_id ?
????????????????????
? COL1 ? 1 ?
? COL2 ? 2 ?
? COL3 ? 3 ?
? COL4 ? 4 ?
????????????????????
Run Code Online (Sandbox Code Playgroud)
现在删除一列:
ALTER TABLE dbo.SEE_COLUMN_ORDER DROP COLUMN COL3;
Run Code Online (Sandbox Code Playgroud)
新结果:
????????????????????
? name ? column_id ?
????????????????????
? COL1 ? 1 ?
? COL2 ? 2 ?
? COL4 ? 4 ?
????????????????????
Run Code Online (Sandbox Code Playgroud)
现在添加一列:
ALTER TABLE dbo.SEE_COLUMN_ORDER ADD COL3 INT;
Run Code Online (Sandbox Code Playgroud)
新结果:
????????????????????
? name ? column_id ?
????????????????????
? COL1 ? 1 ?
? COL2 ? 2 ?
? COL4 ? 4 ?
? COL3 ? 5 ?
????????????????????
Run Code Online (Sandbox Code Playgroud)
没有 acolumn_id为 3 的列。据我所知,没有已知或支持的方式使COL3acolumn_id为 3。如果您希望该列显示在SELECT *查询中的第三位,您可以针对表定义一个视图名称和不同的架构,或者您可以删除并使用所需的列顺序重新创建整个表。
重新创建整个表听起来像是一个缓慢的操作,但有时它可能比更新单个列的所有行更快。这将取决于您的系统、表结构和表中的数据。删除和创建可能更快的一种情况是,如果您的事务日志写入是瓶颈。使用 simple 恢复模型,您可以创建具有最少日志记录的新表,与UPDATE. 删除和重新创建可以更快的另一种情况是,这是否UPDATE会导致大量页面拆分。可以构造一个表和一个UPDATE将每个数据页分成两部分的表(一个UPDATE到一个列值,使其NULL据我所知不会这样做)。作为一般经验法则,除非操作可以最少记录,否则无论恢复模式如何,都会将相同数量的数据保存到日志中。UPDATE从来没有最少记录,因此切换到简单不会减少该操作的事务日志要求。
为了解决与性能有关的部分问题,需要注意的一件重要事情是向现有表添加和删除列是优化操作,其具有固定成本,不会随着表中的数据量而扩展。为了看到这一点,我将查看在事务日志中为操作记录了多少数据(删除和添加列可以在事务中回滚)。我正在针对 SQL Server 2016 进行测试。
这是示例数据:
DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_2;
CREATE TABLE dbo.X_COLUMN_WIPE_2 (
ID INT NOT NULL IDENTITY (1, 1),
COL_TO_WIPE VARCHAR(1) NULL,
FILLER VARCHAR(100) NULL,
PRIMARY KEY (ID)
);
-- 2536 rows
INSERT INTO dbo.X_COLUMN_WIPE_2 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1;
Run Code Online (Sandbox Code Playgroud)
根据sys.dm_tran_database_transactions,UPDATE将 324752 个日志字节写入事务日志:
BEGIN TRANSACTION
UPDATE dbo.X_COLUMN_WIPE_2 SET COL_TO_WIPE = NULL;
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
删除和添加一列只会将 1992 个日志字节写入日志:
BEGIN TRANSACTION
ALTER TABLE dbo.X_COLUMN_WIPE_2 DROP COLUMN COL_TO_WIPE;
ALTER TABLE dbo.X_COLUMN_WIPE_2 ADD NEW_COLUMN VARCHAR(1) NULL;
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
现在用更多数据进行测试:
DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_3;
CREATE TABLE dbo.X_COLUMN_WIPE_3 (
ID INT NOT NULL IDENTITY (1, 1),
COL_TO_WIPE VARCHAR(1) NULL,
FILLER VARCHAR(100) NULL,
PRIMARY KEY (ID)
);
-- 6431296 rows
INSERT INTO dbo.X_COLUMN_WIPE_3 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
Run Code Online (Sandbox Code Playgroud)
现在同样UPDATE将 721979808 字节写入事务日志,但删除和创建列仍然只写入 1992 字节。
可以使用未记录的 DBCC PAGE深入了解为什么会发生这种情况。这是我在系统上运行代码时的示例(复制和粘贴代码将不起作用,因为页码会有所不同):
DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_4;
CREATE TABLE dbo.X_COLUMN_WIPE_4 (
ID INT NOT NULL IDENTITY (1, 1),
COL_TO_WIPE VARCHAR(1) NULL,
FILLER VARCHAR(100) NULL,
PRIMARY KEY (ID)
);
INSERT INTO dbo.X_COLUMN_WIPE_4 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1;
-- first first data page
DBCC IND('SE_DB',X_COLUMN_WIPE_4,-1)
-- view first data page
DBCC TRACEON(3604)
DBCC PAGE('SE_DB',1,1192232,3);
BEGIN TRANSACTION
UPDATE dbo.X_COLUMN_WIPE_4 SET COL_TO_WIPE = NULL;
DBCC PAGE('SE_DB',1,1192232,3);
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
正如保罗怀特所说,我在这方面非常业余,但无论如何我都会给出我的解释。以下是更新前后表中第一条记录的页面内容的差异:
我在我怀疑是红色的重要页面下划线。请注意,许多物理长度已更改,并且COL_TO_WIPE行数据中不再存在值“A” 。看起来UPDATE页面中存储的大部分数据都发生了变化。
这是原始表和COL_TO_WIPE删除列后的差异:
差异的数量比以前小得多。没有任何行数据被更改。我相信只有在表页之外的表上执行了元数据操作,所以DBCC PAGE这里显示的所有更改都是逻辑的,而不是物理的。
这是原始表与删除列并添加新列之后的差异:
和以前一样,似乎没有任何物理差异。实际数据看起来完全一样。只有物理长度为 0 的列定义。
由于这里的问题似乎与如何在页面中逐行存储日期有关,因此对于作为以列格式存储的聚集列存储索引创建的表,结果可能会有所不同。毕竟,认为更新列值应该只影响一列而不是表中的所有数据是合理的。不幸的是,对于这种情况,当前UPDATE针对 CCI 的实现是逻辑删除和插入。因此,如果将列的所有行更新为NULL,SQL Server 会将表中的所有现有行标记为逻辑删除,并为所有行创建新的行组。
总而言之,除了重新创建整个表格之外,您已经提到了我所知道的用于解决您的问题的问题中的所有“聪明”方法。NULL在不破坏表中前一列顺序的情况下,有一种“即时”方式将列更新为位于表的中间是不太可能的。
| 归档时间: |
|
| 查看次数: |
5521 次 |
| 最近记录: |