SQL 服务器对数据使用加密导致 SECDoClientHandshake 错误
SQL Server Use Encryption for Data results in SECDoClientHandshake error
精简版
- ConnectionString:
Provider=SQLOLEDB;Data Source=hydrogen;
User ID=lgilmore;Password=squeegebeckenheim;
Use Encryption for Data=true;
Trust Server Certificate=true;
给出错误:
[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error
背景
我正在尝试加密 SQL 服务器和 OLEDB 客户端(例如 ADO、SQLOLEDB)之间的通信。
我们从 Microsoft 那里注意到(除非您自己配置了一个),SQL 服务器会自动生成一个用于保护登录过程的自签名证书:
Using Encryption Without Validation
SQL Server always encrypts network packets associated with logging in. If no certificate has been provisioned on the server when it starts up, SQL Server generates a self-signed certificate which is used to encrypt login packets.
我们也可以选择一直加密
我们的客户可以要求我们一直使用加密 - 而不仅仅是登录过程。同样,来自微软:
Applications may also request encryption of all network traffic by using connection string keywords or connection properties. The keywords are:
"Encrypt"
: for ODBC and OLE DB when using a provider string with IDbInitialize::Initialize, or
"Use Encryption for Data"
for ADO and OLE DB when using an initialization string with IDataInitialize.
这个Use Encryption for Data
连接字符串关键字意味着我们要使用:
- 不仅对登录过程使用加密
- 但也对数据使用加密
并且我们使用"Trust Server Certificate"来信任自签名证书
SQL服务器自动生成的自签名证书是自签名的。通常,客户端驱动程序会遍历证书的信任链,以查看证书是否有效。使用自动自签名证书,连接将失败。
但是还有一个关键字强制客户端接受服务器证书:Trust Server Certificate:
To enable encryption to be used when a certificate has not been provisioned on the server, applications may use the "TrustServerCertificate"
keyword or its associated connection attribute to guarantee that encryption takes place. To guarantee encryption even when a server certificate has not been provisioned, an application may request encryption and "TrustServerCertificate"
.
When Trust Server Certificate
is set to true, the transport layer will use SSL to encrypt the channel and bypass walking the certificate chain to validate trust.
所以我们有两个关键词:
Use Encryption for Data=true
:选择加密
Trust Server Certificate=true
: 信任自签名证书
函数代码示例
现在是一些可重现性最低的代码。
- 我正在使用 OLEDB(例如 ADO)。
- 我不使用ADO.NET。
- 我不使用ODBC。
- 我没有使用
SqlClient
(即我没有使用ADO.NET)
- 我正在使用 SQLOLEDB(即 Microsoft OLE DB Provider for SQL Server)。
- 我不使用SQL服务器本机客户端(例如SQLNCLI、SQLNCLI11)
为了进一步说明我正在使用 OLEDB,我将给出一个直接使用 OLEDB 的代码示例(尽管对于 ADO 都是如此;它只是 OLDB 的简单包装):
String connectionString =
"Provider=SQLOLEDB;Data Source=hydrogen;"+
"User ID=lgilmore;Password=squeegebeckenheim;";
//DAInitialize helper class parses the connection string
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);
//Ask DAInitialize to create the SQLOLEDB class for us and set it up
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
//Connect to SQL Server
dataSource.Initialize(); //actually opens the database connection
我们的初始代码示例还没有请求加密;所以我们能够连接也就不足为奇了。与 SQL 服务器的连接已顺利建立。 (我们可以使用 SQL Profiler 确认连接)。
代码示例 - 启用加密
我们将代码重构为接受连接字符串的函数:
String connectionString =
"Provider=SQLOLEDB;Data Source=hydrogen;"+
"User ID=lgilmore;Password=squeegebeckenheim;"+
"Use Encryption for Data=true"; //opt-in to encryption of data
IDbInitialize dataSource = ConnectToDataSource(connectionString);
使用我们的新辅助函数:
IDbInitialize ConnectToDataSource(String connectionString)
{
//DAInitialize helper class parses the connection string
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);
//Ask DAInitialize to create the SQLOLEDB class for us and set it up
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
//Connect to SQL Server
dataSource.Initialize(); //actually opens the database connection
}
我们预计此代码在连接到数据库时会失败。确实如此:
[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error
信任服务器证书也失败
我们现在将连接字符串更新为 Trust Server Certificate=true
:
String connectionString =
"Provider=SQLOLEDB;Data Source=hydrogen;"+
"User ID=lgilmore;Password=squeegebeckenheim;"+
"Use Encryption for Data=true;"+ //opt-into encryption of data
"Trust Server Certificate=true"; //trust the self-signed server cert
但它仍然失败并出现同样的错误:
[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error
SQLOLEDB 无法识别信任服务器证书
如何指定Trust Server Certificate
有很多困惑。从直接初始化 OLEDB 提供程序,到使用 IDataInitialize
,再到 SQL 服务器 Native Client (SQLNCLI),再到 ADO.net SqlClient,和Java ODBC,有变体:
Trust Server Certificate=true
Trust Server Certificate=yes
TrustServerCertificate=true
TrustServerCertificate=yes
以上四种变体我都试过了; none 有效。
询问提供商是否识别它
我使用 DataLinks
class 规范化我的连接字符串:
String CanonicalizeConnectionString(String cs)
{
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_DataLinks);
IDBInitialize datasource;
dataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
String result;
dataInit.GetInitializationString(dataSource, true, out result);
return result;
}
这给了我原来的和解析的连接字符串:
- 原文:
Provider=SQLOLEDB;Data Source=hydrogen;User ID=lgilmore;Password=squeegebeckenheim;Use Encryption for Data=true;Trust Server Certificate=true;
- 规范化:
Provider=SQLOLEDB.1;Password=squeegebeckenheim;User ID=lgilmore;Data Source=hydrogen;Extended Properties="Trust Server Certificate=true";Use Encryption for Data=True
鉴于 Trust Server Certificate
已被放入无法识别属性的包罗万象中:
Extended Properties="Trust Server Certificate=true"
无法识别的事实意味着我可能不得不继续寻找正确的神奇语法。
我想通了。
向数据源询问它支持的所有属性:
String EnumerateAllProperties(String connectionString)
{
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
String s = "";
Int32 nSets;
PDBPropInfoSet pi;
POleStr desc;
(dataSource as IDBProperties).GetPropertyInfo(0, null, ref nSets, out pi, ref desc);
for (int i=0, i <nSets, i++)
{
s = s+CRLF+
PropSetGuidToStr(pi.guidPropertySet);
for (int j=0, j < pi[i].cPropertyInfos, j++)
{
s = s+CRLF+
IntToStr(pi[i].rgPropertyInfos[j].dwPropertyID)+TAB+
PWideChar(pi[i].rgPropertyInfos[j].pwszDescription)+TAB+
VTypeToStr(pi[i].rgPropertyInfos[j].vtType);
}
s = s+CRLF;
}
return s;
}
我意识到 SQLOLEDB(SQL 服务器的 Microsoft OLE DB 提供程序)不支持 Trust Server Certificate
:
SQLOLEDB - DBPROPSET_DBINIT
属性 设置
PropertyID
Description
Type
SQLOLEDB
7
Integrated Security
VT_BSTR
Yes
9
Password
VT_BSTR
Yes
11
Persist Security Info
VT_BOOL
Yes
12
User ID
VT_BSTR
Yes
59
Data Source
VT_BSTR
Yes
60
Window Handle
VT_I4
Yes
64
Prompt
VT_I2
Yes
66
Connect Timeout
VT_I4
Yes
160
Extended Properties
VT_BSTR
Yes
186
Locale Identifier
VT_I4
Yes
233
Initial Catalog
VT_BSTR
Yes
248
OLE DB Services
VT_I4
Yes
284
General Timeout
VT_I4
Yes
SQLOLEDB - DBPROPSET_SQLSERVERDBINIT
属性 设置
PropertyID
Description
Type
SQLOLEDB
4
Current Language
VT_BSTR
Yes
5
Network Address
VT_BSTR
Yes
6
Network Library
VT_BSTR
Yes
7
Use Procedure for Prepare
VT_I4
Yes
8
Auto Translate
VT_BOOL
Yes
9
Packet Size
VT_I4
Yes
10
Application Name
VT_BSTR
Yes
11
Workstation ID
VT_BSTR
Yes
12
Initial File Name
VT_BSTR
Yes
13
Use Encryption for Data
VT_BOOL
Yes
14
Replication server name connect option
VT_BSTR
Yes
15
Tag with column collation when possible
VT_BOOL
Yes
如果将其与 SQL Server Native Client 11.0
进行比较
SQL 服务器本机客户端 11.0
SQLNCLI11 - DBPROPSET_DBINIT
属性 设置
PropertyID
Description
Type
SQLOLEDB
SQLNCLI11
7
Integrated Security
VT_BSTR
Yes
Yes
9
Password
VT_BSTR
Yes
Yes
11
Persist Security Info
VT_BOOL
Yes
Yes
12
User ID
VT_BSTR
Yes
Yes
59
Data Source
VT_BSTR
Yes
Yes
60
Window Handle
VT_I4
Yes
Yes
64
Prompt
VT_I2
Yes
Yes
66
Connect Timeout
VT_I4
Yes
Yes
160
Extended Properties
VT_BSTR
Yes
Yes
186
Locale Identifier
VT_I4
Yes
Yes
200
Asynchronous Processing
VT_I4
No
Yes
233
Initial Catalog
VT_BSTR
Yes
Yes
248
OLE DB Services
VT_I4
Yes
Yes
284
General Timeout
VT_I4
Yes
Yes
SQLNCLI11 - DBPROPSET_SQLSERVERDBINIT
属性 设置
PropertyID
Description
Type
SQLOLEDB
SQLNCLI11
4
Current Language
VT_BSTR
Yes
Yes
5
Network Address
VT_BSTR
Yes
Yes
6
Network Library
VT_BSTR
Yes
Yes
7
Use Procedure for Prepare
VT_I4
Yes
Yes
8
Auto Translate
VT_BOOL
Yes
Yes
9
Packet Size
VT_I4
Yes
Yes
10
Application Name
VT_BSTR
Yes
Yes
11
Workstation ID
VT_BSTR
Yes
Yes
12
Initial File Name
VT_BSTR
Yes
Yes
13
Use Encryption for Data
VT_BOOL
Yes
Yes
14
Replication server name connect option
VT_BSTR
Yes
Yes
15
Tag with column collation when possible
VT_BOOL
Yes
Yes
16
MARS Connection
VT_BOOL
No
Yes
18
Failover Partner
VT_BSTR
No
Yes
19
Old Password
VT_BSTR
No
Yes
20
DataTypeCompatibility
VT_UI2
No
Yes
21
Trust Server Certificate
VT_BOOL
No
Yes
22
Server SPN
VT_BSTR
No
Yes
23
Failover Partner SPN
VT_BSTR
No
Yes
24
Application Intent
VT_BSTR
No
Yes
您可以看到 deprecated native client supports Trust Server Certificate
, while the supported OLE DB 客户端没有:
| 21 | Trust Server Certificate | VT_BOOL | No | Yes |
这很不幸,因为 “无法修复” ODBC 驱动程序中的错误 (, 2, 3)
精简版
- ConnectionString:
Provider=SQLOLEDB;Data Source=hydrogen;
User ID=lgilmore;Password=squeegebeckenheim;
Use Encryption for Data=true;
Trust Server Certificate=true;
给出错误:
[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error
背景
我正在尝试加密 SQL 服务器和 OLEDB 客户端(例如 ADO、SQLOLEDB)之间的通信。
我们从 Microsoft 那里注意到(除非您自己配置了一个),SQL 服务器会自动生成一个用于保护登录过程的自签名证书:
Using Encryption Without Validation
SQL Server always encrypts network packets associated with logging in. If no certificate has been provisioned on the server when it starts up, SQL Server generates a self-signed certificate which is used to encrypt login packets.
我们也可以选择一直加密
我们的客户可以要求我们一直使用加密 - 而不仅仅是登录过程。同样,来自微软:
Applications may also request encryption of all network traffic by using connection string keywords or connection properties. The keywords are:
"Encrypt"
: for ODBC and OLE DB when using a provider string with IDbInitialize::Initialize, or"Use Encryption for Data"
for ADO and OLE DB when using an initialization string with IDataInitialize.
这个Use Encryption for Data
连接字符串关键字意味着我们要使用:
- 不仅对登录过程使用加密
- 但也对数据使用加密
并且我们使用"Trust Server Certificate"来信任自签名证书
SQL服务器自动生成的自签名证书是自签名的。通常,客户端驱动程序会遍历证书的信任链,以查看证书是否有效。使用自动自签名证书,连接将失败。
但是还有一个关键字强制客户端接受服务器证书:Trust Server Certificate:
To enable encryption to be used when a certificate has not been provisioned on the server, applications may use the
"TrustServerCertificate"
keyword or its associated connection attribute to guarantee that encryption takes place. To guarantee encryption even when a server certificate has not been provisioned, an application may request encryption and"TrustServerCertificate"
.When
Trust Server Certificate
is set to true, the transport layer will use SSL to encrypt the channel and bypass walking the certificate chain to validate trust.
所以我们有两个关键词:
Use Encryption for Data=true
:选择加密Trust Server Certificate=true
: 信任自签名证书
函数代码示例
现在是一些可重现性最低的代码。
- 我正在使用 OLEDB(例如 ADO)。
- 我不使用ADO.NET。
- 我不使用ODBC。
- 我没有使用
SqlClient
(即我没有使用ADO.NET) - 我正在使用 SQLOLEDB(即 Microsoft OLE DB Provider for SQL Server)。
- 我不使用SQL服务器本机客户端(例如SQLNCLI、SQLNCLI11)
为了进一步说明我正在使用 OLEDB,我将给出一个直接使用 OLEDB 的代码示例(尽管对于 ADO 都是如此;它只是 OLDB 的简单包装):
String connectionString =
"Provider=SQLOLEDB;Data Source=hydrogen;"+
"User ID=lgilmore;Password=squeegebeckenheim;";
//DAInitialize helper class parses the connection string
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);
//Ask DAInitialize to create the SQLOLEDB class for us and set it up
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
//Connect to SQL Server
dataSource.Initialize(); //actually opens the database connection
我们的初始代码示例还没有请求加密;所以我们能够连接也就不足为奇了。与 SQL 服务器的连接已顺利建立。 (我们可以使用 SQL Profiler 确认连接)。
代码示例 - 启用加密
我们将代码重构为接受连接字符串的函数:
String connectionString =
"Provider=SQLOLEDB;Data Source=hydrogen;"+
"User ID=lgilmore;Password=squeegebeckenheim;"+
"Use Encryption for Data=true"; //opt-in to encryption of data
IDbInitialize dataSource = ConnectToDataSource(connectionString);
使用我们的新辅助函数:
IDbInitialize ConnectToDataSource(String connectionString)
{
//DAInitialize helper class parses the connection string
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);
//Ask DAInitialize to create the SQLOLEDB class for us and set it up
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
//Connect to SQL Server
dataSource.Initialize(); //actually opens the database connection
}
我们预计此代码在连接到数据库时会失败。确实如此:
[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error
信任服务器证书也失败
我们现在将连接字符串更新为 Trust Server Certificate=true
:
String connectionString =
"Provider=SQLOLEDB;Data Source=hydrogen;"+
"User ID=lgilmore;Password=squeegebeckenheim;"+
"Use Encryption for Data=true;"+ //opt-into encryption of data
"Trust Server Certificate=true"; //trust the self-signed server cert
但它仍然失败并出现同样的错误:
[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error
SQLOLEDB 无法识别信任服务器证书
如何指定Trust Server Certificate
有很多困惑。从直接初始化 OLEDB 提供程序,到使用 IDataInitialize
,再到 SQL 服务器 Native Client (SQLNCLI),再到 ADO.net SqlClient,和Java ODBC,有变体:
Trust Server Certificate=true
Trust Server Certificate=yes
TrustServerCertificate=true
TrustServerCertificate=yes
以上四种变体我都试过了; none 有效。
询问提供商是否识别它
我使用 DataLinks
class 规范化我的连接字符串:
String CanonicalizeConnectionString(String cs)
{
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_DataLinks);
IDBInitialize datasource;
dataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
String result;
dataInit.GetInitializationString(dataSource, true, out result);
return result;
}
这给了我原来的和解析的连接字符串:
- 原文:
Provider=SQLOLEDB;Data Source=hydrogen;User ID=lgilmore;Password=squeegebeckenheim;Use Encryption for Data=true;Trust Server Certificate=true;
- 规范化:
Provider=SQLOLEDB.1;Password=squeegebeckenheim;User ID=lgilmore;Data Source=hydrogen;Extended Properties="Trust Server Certificate=true";Use Encryption for Data=True
鉴于 Trust Server Certificate
已被放入无法识别属性的包罗万象中:
Extended Properties="Trust Server Certificate=true"
无法识别的事实意味着我可能不得不继续寻找正确的神奇语法。
我想通了。
向数据源询问它支持的所有属性:
String EnumerateAllProperties(String connectionString)
{
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);
String s = "";
Int32 nSets;
PDBPropInfoSet pi;
POleStr desc;
(dataSource as IDBProperties).GetPropertyInfo(0, null, ref nSets, out pi, ref desc);
for (int i=0, i <nSets, i++)
{
s = s+CRLF+
PropSetGuidToStr(pi.guidPropertySet);
for (int j=0, j < pi[i].cPropertyInfos, j++)
{
s = s+CRLF+
IntToStr(pi[i].rgPropertyInfos[j].dwPropertyID)+TAB+
PWideChar(pi[i].rgPropertyInfos[j].pwszDescription)+TAB+
VTypeToStr(pi[i].rgPropertyInfos[j].vtType);
}
s = s+CRLF;
}
return s;
}
我意识到 SQLOLEDB(SQL 服务器的 Microsoft OLE DB 提供程序)不支持 Trust Server Certificate
:
SQLOLEDB - DBPROPSET_DBINIT
属性 设置
PropertyID | Description | Type | SQLOLEDB |
---|---|---|---|
7 | Integrated Security | VT_BSTR | Yes |
9 | Password | VT_BSTR | Yes |
11 | Persist Security Info | VT_BOOL | Yes |
12 | User ID | VT_BSTR | Yes |
59 | Data Source | VT_BSTR | Yes |
60 | Window Handle | VT_I4 | Yes |
64 | Prompt | VT_I2 | Yes |
66 | Connect Timeout | VT_I4 | Yes |
160 | Extended Properties | VT_BSTR | Yes |
186 | Locale Identifier | VT_I4 | Yes |
233 | Initial Catalog | VT_BSTR | Yes |
248 | OLE DB Services | VT_I4 | Yes |
284 | General Timeout | VT_I4 | Yes |
SQLOLEDB - DBPROPSET_SQLSERVERDBINIT
属性 设置
PropertyID | Description | Type | SQLOLEDB |
---|---|---|---|
4 | Current Language | VT_BSTR | Yes |
5 | Network Address | VT_BSTR | Yes |
6 | Network Library | VT_BSTR | Yes |
7 | Use Procedure for Prepare | VT_I4 | Yes |
8 | Auto Translate | VT_BOOL | Yes |
9 | Packet Size | VT_I4 | Yes |
10 | Application Name | VT_BSTR | Yes |
11 | Workstation ID | VT_BSTR | Yes |
12 | Initial File Name | VT_BSTR | Yes |
13 | Use Encryption for Data | VT_BOOL | Yes |
14 | Replication server name connect option | VT_BSTR | Yes |
15 | Tag with column collation when possible | VT_BOOL | Yes |
如果将其与 SQL Server Native Client 11.0
进行比较SQL 服务器本机客户端 11.0
SQLNCLI11 - DBPROPSET_DBINIT
属性 设置
PropertyID | Description | Type | SQLOLEDB | SQLNCLI11 |
---|---|---|---|---|
7 | Integrated Security | VT_BSTR | Yes | Yes |
9 | Password | VT_BSTR | Yes | Yes |
11 | Persist Security Info | VT_BOOL | Yes | Yes |
12 | User ID | VT_BSTR | Yes | Yes |
59 | Data Source | VT_BSTR | Yes | Yes |
60 | Window Handle | VT_I4 | Yes | Yes |
64 | Prompt | VT_I2 | Yes | Yes |
66 | Connect Timeout | VT_I4 | Yes | Yes |
160 | Extended Properties | VT_BSTR | Yes | Yes |
186 | Locale Identifier | VT_I4 | Yes | Yes |
200 | Asynchronous Processing | VT_I4 | No | Yes |
233 | Initial Catalog | VT_BSTR | Yes | Yes |
248 | OLE DB Services | VT_I4 | Yes | Yes |
284 | General Timeout | VT_I4 | Yes | Yes |
SQLNCLI11 - DBPROPSET_SQLSERVERDBINIT
属性 设置
PropertyID | Description | Type | SQLOLEDB | SQLNCLI11 |
---|---|---|---|---|
4 | Current Language | VT_BSTR | Yes | Yes |
5 | Network Address | VT_BSTR | Yes | Yes |
6 | Network Library | VT_BSTR | Yes | Yes |
7 | Use Procedure for Prepare | VT_I4 | Yes | Yes |
8 | Auto Translate | VT_BOOL | Yes | Yes |
9 | Packet Size | VT_I4 | Yes | Yes |
10 | Application Name | VT_BSTR | Yes | Yes |
11 | Workstation ID | VT_BSTR | Yes | Yes |
12 | Initial File Name | VT_BSTR | Yes | Yes |
13 | Use Encryption for Data | VT_BOOL | Yes | Yes |
14 | Replication server name connect option | VT_BSTR | Yes | Yes |
15 | Tag with column collation when possible | VT_BOOL | Yes | Yes |
16 | MARS Connection | VT_BOOL | No | Yes |
18 | Failover Partner | VT_BSTR | No | Yes |
19 | Old Password | VT_BSTR | No | Yes |
20 | DataTypeCompatibility | VT_UI2 | No | Yes |
21 | Trust Server Certificate | VT_BOOL | No | Yes |
22 | Server SPN | VT_BSTR | No | Yes |
23 | Failover Partner SPN | VT_BSTR | No | Yes |
24 | Application Intent | VT_BSTR | No | Yes |
您可以看到 deprecated native client supports Trust Server Certificate
, while the supported OLE DB 客户端没有:
| 21 | Trust Server Certificate | VT_BOOL | No | Yes |
这很不幸,因为 “无法修复” ODBC 驱动程序中的错误 (