大多数人把 SQL Server 的主机名当作完整地址,把 VPN 当作要么放行、要么不放行。实际上,命名实例会让客户端在 DNS 之后再做一次查找,而隧道最常破坏的正是这次查找。显式端点可以绕开它,所以tcp:host,port可能能用,而host\INSTANCE却超时。区别在于找到服务,与获得使用它的许可。

误解:一个主机名对应一个端口

主机名解析成一个地址,它的职责到此为止。命名实例附加了一个实例名,服务器必须把它翻译成 TCP 端口,客户端无法猜出。1433 端口是默认实例的默认值,不是机器上每个实例的属性。把两者当成一回事,就会出现典型症状:ping 通、DNS 通、扫描 1433 什么也没有,于是结论是网络屏蔽了 SQL。

第二个假设也同样站不住:只要打开 TCP 1433 就够了。如果实例监听在别处,开放的 1433 只能证明那里有别的服务。端口必须与监听器匹配,而监听器是服务端的配置细节,这一点应当去问,而不是靠猜。

客户端面对命名实例到底做了什么

解析主机之后,客户端向 UDP 1434 上的 SQL Browser 发出发现请求,询问命名实例使用哪个端口。浏览器服务从服务器自己的注册表作答,然后客户端再向该端口建立 TCP 连接。在尝试登录之前,已经牵涉两个协议和两个端口。

nslookup sqlhost.corp.example
nc -vzu sqlhost.corp.example 1434
Get-Service -Name SQLBrowser

这就是为什么只封 UDP 会产生看起来像主机已死的超时。如果浏览器服务被停止或禁用,即使数据库引擎在运行、它的 TCP 端口是开放的,发现请求也得不到回应。如果实例配置了固定端口,除非直接给出了端点,客户端仍然会去询问。这些失败在应用报错里看起来一模一样,却需要不同的修复。

浏览器服务只是发现环节的便利,不是数据库本身。停掉它不会停掉引擎,启动它也不会打开实例的 TCP 端口。

为什么名字不通时显式端点却能通

显式端点把查找从流程中去掉:客户端直接连到你指定的地址和端口。这首先是诊断,而不是自动的永久修复,因为如果实例之后被改成动态端口,端口会变。在得出结论之前,用同一个客户端、同一个账号比较两种写法。

sqlcmd -S "tcp:sqlhost.corp.example,51433" -U app_login -Q "SELECT @@SERVERNAME"
Test-NetConnection sqlhost.corp.example -Port 51433

如果显式写法通而命名写法不通,缺口就在发现环节,你可以选择怎么补:允许 UDP 1434 通过隧道,或者请管理员固定一个静态端口并记录端点。两者都是有意的决定,维护成本不同。把范围内的所有端口都打开则两者皆非,而它恰恰是长日将尽时最容易被提出的选项。把这个选择记在连接字符串所在的地方。

如果两种写法都不通,问题就在发现环节的上游。在责怪浏览器服务之前,先检查监听器、路由,以及实例实际所用端口的访问规则。

发现之后:身份验证与身份是两回事

登录失败与超时是不同的事件,VPN 身份也不会因为有了隧道就变成 SQL 登录。客户端到达正确端口后,数据库引擎会施加自己的身份验证与权限。把两个阶段混在一张工单里,正是发现类问题被派给错误团队的原因。在改动任何东西之前,先读错误号。

如果驱动会校验服务器证书,就保留记录在案的主机名,因为按数字地址连接可能改变那项校验,而不是解决最初的问题。较新的 Microsoft 驱动默认加密,因此名称不匹配会在 TCP 连接成功之后表现为证书错误。那是进展,而不是新的失败。

该抛弃的误解是:SQL Server 的主机名就是完整地址。它是一个主机,再加上命名实例需要的一次查找,而隧道未必承载这次查找。把端口说清楚,把发现路径说清楚,下一次失败就会告诉你该修哪一层。

相关文章