คอลัมน์ Fast change NVARCHAR (4000) เป็น NVARCHAR (260)


12

ฉันมีปัญหาด้านประสิทธิภาพกับหน่วยความจำขนาดใหญ่มากที่จัดการตารางนี้ด้วยNVARCHAR(4000)คอลัมน์สองสามรายการ NVARCHAR(260)สิ่งที่เป็นคอลัมน์เหล่านี้ไม่เคยมีขนาดใหญ่กว่า

การใช้

ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL

ผลใน SQL Server เขียนใหม่ทั้งตาราง (และใช้ขนาดตาราง 2x ในพื้นที่บันทึก) ซึ่งเป็นพันล้านแถวเท่านั้นที่จะเปลี่ยนแปลงอะไรไม่มีตัวเลือก การเพิ่มความกว้างของคอลัมน์ไม่มีปัญหานี้ แต่เป็นการลดลง

ฉันได้ลองสร้างข้อ จำกัดCHECK (DATALENGTH([col]) <= 520)หรือCHECK (LEN([col]) <= 260)และ SQL Server ยังคงตัดสินใจที่จะเขียนตารางใหม่ทั้งหมด

มีวิธีใดบ้างในการแก้ไขชนิดข้อมูลคอลัมน์เป็นการดำเนินการเฉพาะเมทาดาทา ไม่มีค่าใช้จ่ายในการเขียนใหม่ทั้งตาราง? ฉันใช้ SQL Server 2017 (14.0.2027.2 และ 14.0.3192.2)

นี่คือตาราง DDL ตัวอย่างที่จะใช้ในการทำซ้ำ:

CREATE TABLE [table](
    id INT IDENTITY(1,1) NOT NULL,
    [col] NVARCHAR(4000) NULL,
    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

ALTERและเรียกใช้แล้ว

คำตอบ:


16

ฉันไม่รู้วิธีที่จะบรรลุสิ่งที่คุณกำลังมองหาที่นี่โดยตรง โปรดทราบว่าขณะนี้เครื่องมือเพิ่มประสิทธิภาพการสืบค้นไม่ฉลาดพอที่จะคำนึงถึงข้อ จำกัด สำหรับการคำนวณการจัดสรรหน่วยความจำดังนั้นข้อ จำกัด จะไม่ช่วย วิธีการสองสามอย่างที่หลีกเลี่ยงการเขียนข้อมูลของตารางใหม่:

  1. เลือกคอลัมน์เป็น NVARCHAR (260) ในรหัสทั้งหมดที่ใช้ เครื่องมือเพิ่มประสิทธิภาพการสืบค้นจะคำนวณการให้สิทธิ์หน่วยความจำโดยใช้ชนิดข้อมูลแบบคาสท์แทนข้อมูลดิบ
  2. เปลี่ยนชื่อตารางและสร้างมุมมองที่นำแสดงโดยแทน สิ่งนี้จะสำเร็จเช่นเดียวกับตัวเลือก 1 แต่อาจ จำกัด จำนวนรหัสที่คุณต้องการอัปเดต
  3. สร้างคอลัมน์ที่คำนวณแล้วแบบไม่คงที่ด้วยชนิดข้อมูลที่ถูกต้องและให้แบบสอบถามทั้งหมดของคุณเลือกจากคอลัมน์นั้นแทนที่จะเป็นคอลัมน์ดั้งเดิม
  4. เปลี่ยนชื่อคอลัมน์ที่มีอยู่และเพิ่มคอลัมน์ที่คำนวณด้วยชื่อเดิม จากนั้นปรับคิวรีทั้งหมดของคุณเพื่อทำการอัพเดตหรือแทรกเป็นคอลัมน์เดิมเพื่อใช้ชื่อคอลัมน์ใหม่แทน

15

มีวิธีใดบ้างในการแก้ไขชนิดข้อมูลคอลัมน์เป็นการดำเนินการเฉพาะเมทาดาทา

ฉันไม่คิดอย่างนั้นนี่เป็นวิธีที่ผลิตภัณฑ์ใช้งานได้ในขณะนี้ มีบางวิธีการแก้ปัญหาที่ดีจริงๆข้อ จำกัด นี้เสนอเป็นคำตอบของโจ

... ส่งผลให้ SQL Server เขียนใหม่ทั้งตาราง (และใช้ขนาดตาราง 2x ในพื้นที่บันทึก)

ฉันจะตอบกลับสองส่วนของคำแถลงนั้นแยกจากกัน

เขียนใหม่ตาราง

อย่างที่ฉันได้กล่าวไปแล้วไม่มีทางใดที่จะหลีกเลี่ยงสิ่งนี้ได้ ดูเหมือนว่าจะเป็นความจริงของสถานการณ์แม้ว่ามันจะไม่สมเหตุสมผลจากมุมมองของเราในฐานะลูกค้า

กำลังมองหาที่DBCC PAGEก่อนและหลังการเปลี่ยนแปลงคอลัมน์ 4000-260 แสดงให้เห็นว่าข้อมูลทั้งหมดซ้ำในหน้าข้อมูล (ตารางการทดสอบของฉันมี'A'260 ครั้งในแถว):

สกรีนช็อตของส่วนข้อมูลของหน้า dbcc ก่อนและหลัง

ณ จุดนี้มีสำเนาของข้อมูลเดียวกันที่แน่นอนในหน้า คอลัมน์ "เก่า" จะถูกลบโดยหลักแล้ว (id เปลี่ยนจาก id = 2 เป็น id = 67108865) และคอลัมน์รุ่น "ใหม่" ได้รับการอัปเดตให้ชี้ไปที่ออฟเซ็ตใหม่ของข้อมูลในหน้า:

สกรีนช็อตของส่วนเมทาดาทาคอลัมน์ของหน้า dbcc ก่อนและหลัง

ใช้ 2x Table Size ใน Log Space

การเพิ่มWITH (ONLINE = ON)ที่ส่วนท้ายของALTERคำสั่งจะลดกิจกรรมการบันทึกลงครึ่งหนึ่งดังนั้นนี่เป็นการปรับปรุงอย่างหนึ่งที่คุณสามารถทำได้เพื่อลดปริมาณการเขียนลงในดิสก์ / พื้นที่ดิสก์ที่จำเป็น

ฉันใช้ชุดทดสอบนี้เพื่อลองใช้:

USE [master];
GO
DROP DATABASE IF EXISTS [248749];
GO
CREATE DATABASE [248749] 
ON PRIMARY 
(
    NAME = N'248749', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749.mdf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
)
LOG ON 
(
    NAME = N'248749_log', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749_log.ldf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
);
GO
USE [248749];
GO

CREATE TABLE dbo.[table]
(
    id int IDENTITY(1,1) NOT NULL,
    [col] nvarchar (4000) NULL,

    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

INSERT INTO dbo.[table]
SELECT TOP (1000000)
    REPLICATE(N'A', 260)
FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2
    CROSS JOIN master.dbo.spt_values v3;
GO

ฉันตรวจสอบsys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)ก่อนและหลังใช้ALTERคำสั่งและนี่คือความแตกต่าง:

ค่าเริ่มต้น (ออฟไลน์) ALTER

  • ไฟล์ข้อมูลเขียน / ไบต์เขียน: 34,809 / 2,193,801,216
  • ล็อกไฟล์เขียน / ไบต์เขียน: 40,953 / 1,484,910,080

ออนไลน์ ALTER

  • ไฟล์ข้อมูลเขียน / ไบต์เขียน: 36,874 / 1,693,745,152 (ลดลง 22.8%)
  • ล็อกไฟล์เขียน / ไบต์เขียน: 24,680 / 866,166,272 (ลดลง 41%)

อย่างที่คุณเห็นมีการลดลงเล็กน้อยในการเขียนไฟล์ข้อมูลและการบันทึกที่สำคัญในการเขียนไฟล์บันทึก


2

ฉันเคยอยู่ในสถานการณ์ที่คล้ายคลึงกันหลายครั้ง

ขั้นตอน:

เพิ่มสีใหม่ของความกว้างที่ต้องการ

ใช้เคอร์เซอร์โดยมีการวนซ้ำสองสามพันครั้ง (อาจเป็นหมื่นหรือสองหมื่น) ต่อการกระทำเพื่อคัดลอกข้อมูลจากคอลัมน์เก่าไปยังคอลัมน์ใหม่

วางคอลัมน์เก่า

เปลี่ยนชื่อคอลัมน์ใหม่เป็นชื่อของคอลัมน์เก่า

Tada!


3
จะทำอย่างไรถ้ามีบางระเบียนที่คุณคัดลอกท้ายถูกอัพเดทหรือลบไปแล้ว?
George.Palacios

1
มันง่ายมากที่จะดำเนินการอย่างหนึ่งสุดท้ายลดลงก่อนupdate table set new_col = old_col where new_col <> old_col; old_col
โคลิน 't ฮาร์ต

1
@ Colin'tHart วิธีการนั้นจะไม่ทำงานกับแถวนับล้าน ... ธุรกรรมมีขนาดใหญ่มากและมันจะบล็อก ....
Jonesome Reinstate Monica

@samsmith ก่อนอื่นให้คุณทำตามที่คุณอธิบายข้างต้น จากนั้นก่อนปล่อยคอลัมน์เดิมหากมีการอัปเดตใด ๆ กับข้อมูลต้นฉบับในระหว่างนี้ให้เรียกใช้คำสั่งการอัปเดตนั้น ควรมีผลกับแถวที่ได้รับการแก้ไขเท่านั้น หรือฉันกำลังพลาดอะไรอยู่?
โคลิน 't ฮาร์ต

เพื่อปกปิดแถวที่อัพเดตในระหว่างกระบวนการพยายามหลีกเลี่ยงการสแกนแบบเต็มwhere new_col <> old_colโดยไม่มีส่วนคำสั่งการกรองอื่น ๆ ที่จะส่งผลให้คุณสามารถเพิ่มทริกเกอร์เพื่อดำเนินการเปลี่ยนแปลงเหล่านี้ได้ในขณะที่เกิดขึ้นและลบออกเมื่อสิ้นสุดกระบวนการ ยังคงเป็นประสิทธิภาพการทำงานที่อาจเกิดขึ้นได้ แต่มีจำนวนน้อยมากตามความยาวของกระบวนการแทนที่จะเป็นหนึ่งการโจมตีครั้งใหญ่ในตอนท้ายอาจเป็นไปได้ (ขึ้นอยู่กับรูปแบบการอัปเดตแอปของคุณสำหรับตาราง) .
David Spillett

1

มีทางเลือกขึ้นอยู่กับพื้นที่ว่างในฐานข้อมูลของคุณ

  1. สร้างสำเนาถูกต้องของตารางของคุณ (เช่นnew_table) ยกเว้นสำหรับคอลัมน์ที่คุณจะได้รับการตัดทอนจากNVARCHAR(4000)การNVARCHAR(260):

    CREATE TABLE [new_table](
        id INT IDENTITY(1,1) NOT NULL,
        [col] NVARCHAR(260) NULL,
        CONSTRAINT [PK_test_new] PRIMARY KEY CLUSTERED (id ASC)
    );
  2. ในหน้าต่างการบำรุงรักษาคัดลอกข้อมูลจากตาราง "เสีย" ( table) ไปยังตาราง "คงที่" ( new_table) ด้วยวิธีง่าย ๆINSERT ... INTO ... SELECT ....:

    SET IDENTITY_INSERT [new_table] ON
    GO
    INSERT id, col INTO [new_table] SELECT id, col from [table]
    GO
    SET IDENTITY_INSERT [new_table] OFF
    GO
  3. เปลี่ยนชื่อตาราง "เสีย" tableเป็นอย่างอื่น:

    EXEC sp_rename 'table', 'old_table';  
  4. เปลี่ยนชื่อตาราง "แก้ไข" new_tableเป็นtable:

    EXEC sp_rename 'new_table', 'table';  
  5. หากทุกอย่างเรียบร้อยให้เปลี่ยนชื่อตาราง "ที่เสียหาย":

     DROP TABLE [old_table]
     GO

ไปแล้ว

ตอบคำถามของคุณ

มีวิธีใดบ้างในการแก้ไขชนิดข้อมูลคอลัมน์เป็นการดำเนินการเฉพาะเมทาดาทา

ไม่ปัจจุบันไม่สามารถทำได้

ไม่มีค่าใช้จ่ายในการเขียนใหม่ทั้งตาราง?

ไม่
( ดูโซลูชันของฉันและอื่น ๆ )


"การแทรกลงในการเลือกจาก" ของคุณจะส่งผลให้บนตารางขนาดใหญ่ (หลายล้านหรือหลายพันล้านแถว) ในธุรกรรมแบบปกติที่อาจทำให้ DB หยุดชะงักเป็นเวลาสิบหรือหลายร้อยนาที (รวมถึงการจัดทำ ldf มหาศาลและอาจทำลายบันทึกการจัดส่งหากใช้งาน)
Jonesome Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.