使用sp_invoke_external_rest_endpoint转存SQL Server中PDF至Blob Storage遇损坏问题
问题
我正尝试将Azure SQL Server数据库表中的PDF文件迁移至Blob Storage,但遇到了问题。计划使用SQL Server存储过程sp_invoke_external_rest_endpoint完成该任务,然而发现文件在传输前被转换为Unicode,导致损坏。
我的代码
DECLARE @bytes VARBINARY(MAX) = NULL; DECLARE @fileName VARCHAR(MAX) = NULL; SELECT TOP 1 @bytes = Bytes, @fileName = FileName FROM #TempPdf OPTION(RECOMPILE); DECLARE @response VARCHAR(MAX) = NULL; DECLARE @storageContainerUrl VARCHAR(MAX) = 'https://mystorage.blob.core.windows.net/testing-pdfs/'; DECLARE @url VARCHAR(MAX) = CONCAT(@storageContainerUrl, @fileName); DECLARE @payload VARCHAR(MAX) = @bytes; DECLARE @payloadLength INT = DATALENGTH(@payload); DECLARE @headers VARCHAR(1000) = JSON_OBJECT ( 'content-length': @payloadLength, 'x-ms-content-length': CAST(@payloadLength AS VARCHAR(20)), 'content-type': 'application/x-www-form-urlencoded', 'Accept': 'application/xml', 'x-ms-blob-type': 'BlockBlob' ); SELECT @bytes AS Bytes, DATALENGTH(@bytes) AS BytesLength, @payload AS Payload, DATALENGTH(@payload) AS PayloadLength; EXEC sp_invoke_external_rest_endpoint @url = @url, @method = 'PUT', @headers = @headers, @payload = @payload, @credential = 'MyCredentials', @response = @response OUTPUT; SELECT CAST(@response AS XML) AS Response;
字节数据(VARBINARY(MAX))和负载(VARCHAR(MAX))的数据长度均为88,698,符合该PDF的正确大小,但Blob Storage中文件大小为127KiB,远大于应有的87KiB。推测是sp_invoke_external_rest_endpoint的@payload参数为NVARCHAR(MAX),将8位变量转换为Unicode导致文件损坏。
请问是否有办法使用sp_invoke_external_rest_endpoint避免文件损坏,或在存储过程中实现该任务的替代方案?如果实在不行,只能用C#创建临时任务来完成。
解决方案
1. sp_invoke_external_rest_endpoint的限制说明
sp_invoke_external_rest_endpoint的@payload参数仅支持NVARCHAR(MAX)类型,传入VARCHAR(MAX)或VARBINARY(MAX)时会自动转换为Unicode编码,导致二进制数据膨胀、文件损坏。目前没有直接绕过该转换的方法,因此该存储过程不适合传输二进制文件。
2. SQL端替代方案
方案一:使用OPENROWSET直接写入Blob
如果SQL Server版本支持,可通过OPENROWSET直接将二进制数据写入Blob Storage,无需编码转换:
INSERT INTO OPENROWSET( BULK N'https://mystorage.blob.core.windows.net/testing-pdfs/your-file-name.pdf', FORMATFILE='nul', SINGLE_BLOB ) SELECT Bytes FROM #TempPdf WHERE FileName = 'your-file-name.pdf';
需提前配置SQL Server的外部数据源和对应凭据,确保具备Blob写入权限。
方案二:利用Azure SQL内置Blob集成
若使用Azure SQL数据库,可通过关联外部数据源直接导出二进制数据:
-- 创建外部数据源 CREATE EXTERNAL DATA SOURCE BlobStorage WITH ( TYPE = BLOB_STORAGE, LOCATION = 'https://mystorage.blob.core.windows.net/testing-pdfs', CREDENTIAL = MyCredentials ); -- 导出单文件 SELECT Bytes FROM #TempPdf WHERE FileName = 'your-file-name.pdf' INTO EXTERNAL FILE BlobStorage WITH (FILE_NAME = 'your-file-name.pdf');
该方式由Azure SQL直接处理二进制传输,完全避免编码问题。
方案三:部署CLR存储过程
编写.NET CLR存储过程,直接读取VARBINARY(MAX)数据并调用Azure Blob SDK上传。需在SQL Server中启用CLR集成,并部署签名的程序集,可精准控制二进制流传输。
3. 外部脚本方案(C#临时任务)
若SQL端方案无法实施,C#临时任务是可靠选择:
- 编写控制台应用,连接Azure SQL读取
VARBINARY(MAX)类型的PDF数据 - 使用
Azure.Storage.BlobsSDK直接上传二进制流到Blob Storage - 可通过Azure Functions或本地任务调度实现批量迁移
这种方式彻底规避SQL端的编码转换问题,适合大规模或复杂的文件迁移场景。
内容的提问来源于stack exchange,提问作者null_pointer

