You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server使用VDI与命名管道还原数据库报38(EOF)错误问题

问题背景

我使用Go与C开发了Windows平台下的Microsoft SQL Server数据库备份/还原应用,通过VDI(虚拟设备接口)和命名管道实现数据传输。目前备份功能运行完全正常,但执行还原操作时流程始终无法完成,SQL Server会中止操作并抛出38错误(EOF,文件已结束)。
当前实现架构:C
侧实现命名管道服务端,同时通过VDI虚拟设备读取管道数据传输至SQL Server;Go侧实现命名管道客户端,测试阶段逻辑为读取磁盘上的有效备份文件写入管道,后续将对接其他写入数据源。经测试,直接读取本地备份文件可正常完成数据库还原,仅当链路中引入命名管道时会触发崩溃。

已完成排查
  • 备份文件有效性确认:应用生成的备份文件可通过SQL Server Management Studio正常完成数据库还原;
  • 命名管道模式确认:命名管道服务端与客户端均采用duplex读写模式,与已公开的同类问题场景不一致。
相关代码与运行现象

C++ 命名管道服务端核心代码(仅展示VDC_Read处理逻辑)

hPipe = CreateNamedPipe(fname,
                        PIPE_ACCESS_DUPLEX,
                        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                        1,
                        1024 * 16,
                        1024 * 16,
                        NMPWAIT_USE_DEFAULT_WAIT,
                        NULL);

if (hPipe != INVALID_HANDLE_VALUE)
{
    printf("[ExecuteTransfer] Named pipe %s created successfully\n", fname);
}

if (ConnectNamedPipe(hPipe, NULL) != FALSE)   // 等待客户端连接管道
{
    printf("[ExecuteTransfer] Starting transfer....\n");
    while (SUCCEEDED (hr))
    {
        dwTransferredBytes = 0;
        printf("[ExecuteTransfer] Executing GetCommand...\n");
        hr = vd->GetCommand(INFINITE, &cmd); 
        if (!SUCCEEDED (hr))
        {
            printf("[ExecuteTransfer] Error executing GetCommand. x%X\n", hr);
            break;
        }
        else 
        {
            printf("[ExecuteTransfer] GetCommand executed successfully\n");
        }
                
        switch (cmd->commandCode)
        {              
            case VDC_Read:
                printf("[ExecuteTransfer] VDC_Read\n");
                printf("[ExecuteTransfer] %d bytes to read....\n",  cmd->size);
                
                fSuccess = ReadFile(hPipe, cmd->buffer, cmd->size, &dwTransferredBytes, NULL);
                gle = GetLastError();
                
                if (fSuccess)
                {
                    completionCode = ERROR_SUCCESS;
                    printf("[ExecuteTransfer] %d bytes read....\n", dwTransferredBytes);
                }
                else
                {   
                    if (gle == ERROR_BROKEN_PIPE)
                        printf("[ExecuteTransfer] Read Error: client disconnected.\n"); 
                    else
                        printf("[ExecuteTransfer] Read Error, GLE=%d.\n", gle); 
                }

                if (dwTransferredBytes == 0)
                {
                    printf("[ExecuteTransfer] 0 bytes transferred. Handling EOF...\n");
                    completionCode = ERROR_HANDLE_EOF;
                }                    
                break; 

Go 命名管道客户端测试代码

测试逻辑为读取本地有效备份文件写入C++创建的命名管道,其中20220531081651.CSYN4HMG0F3.emorydev.FULL为应用生成的可正常还原的备份文件。

vReadFromFileFinished := make(chan bool)
go func() {
    var vFileToRead *os.File
    var err error

    time.Sleep(time.Second * 3) // 等待C++侧创建命名管道
    vFilenameToRead := "c:\\tmp\\20220531081651.CSYN4HMG0F3.emorydev.FULL"
    if vFileToRead, err = os.OpenFile(vFilenameToRead, os.O_RDONLY, 0666); err != nil {
        fmt.Println("[restore] Error opening file", vFilenameToRead)
        os.Exit(-1)
    }
    defer vFileToRead.Close()
    vReader := bufio.NewReader(vFileToRead)
    fmt.Println("[restore] Opening Named Pipe", gvDataSource.MSSQL.GetNamedPipeString())
    vFileToWrite, _ := os.OpenFile(gvDataSource.MSSQL.GetNamedPipeString(), os.O_RDWR, 0666)
    // defer vFileToWrite.Close()
    vWriter := bufio.NewWriter(vFileToWrite)
    var vBytesRead int
    var vBytesWritten int
    var vTotalBytesRead int
    var vTotalBytesWritten int
    vBuffer := make([]byte, 4096)
    for {
        vBytesRead, _ = vReader.Read(vBuffer)
        if vBytesRead == 0 {
            break
        }
        vBytesWritten, _ = vWriter.Write(vBuffer)
        vTotalBytesRead = vTotalBytesRead + vBytesRead
        vTotalBytesWritten = vTotalBytesWritten + vBytesWritten
    }
    fmt.Printf("[restore] Read from %s and write to % s finished\n", vFilenameToRead, gvDataSource.MSSQL.GetNamedPipeString())
    fmt.Printf("[restore] Total bytes read = %d\n", vTotalBytesRead)
    fmt.Printf("[restore] Total bytes written = %d\n", vTotalBytesWritten)
    vReadFromFileFinished <- true
}()

<-vReadFromFileFinished

初始场景运行现象

SQL Server VDI标准执行流程为GetCommand-命令处理-CompleteCommand,前期运行正常,直到单次从管道读取的字节数小于GetCommand要求的字节数后,会触发VDC_ClearError调用,最终GetCommand返回x80770004错误中止。日志显示Go侧读取的备份文件总大小为3105280字节,但实际写入管道的仅为73728字节。Windows事件查看器报错如下:

BackupIoRequest::ReportIoError: 备份设备读取错误,操作系统返回错误38(已到达文件末尾)

调整缓冲区逻辑后的运行现象

修改C++侧逻辑,在GetCommand后、调用CompleteCommand前循环读取直到填满GetCommand要求的缓冲区大小,此时不再触发EOF错误,但执行若干次VDC_Read后GetCommand会异常中止,返回x-7F88FFFC错误,日志显示总传输字节数仅为205312字节,Go侧写入管道字节数为208896字节。此时Windows事件查看器记录SQLVDI触发主动中止,同时SQL Server生成EXCEPTION_ACCESS_VIOLATION(访问违例)的堆栈转储,异常信息显示执行RESTORE DATABASE语句时触发内存访问违规,当前无法定位该访问违例的产生原因,寻求同类问题的解决方案。


内容的提问来源于stack exchange,提问作者Pere

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:15:39