此文章发布于58
个月前,部分信息可能已经过时
,请自行斟酌确认。
问题描述
C# 使用 MySQL.Data.dll
连接 MySQL 数据库,报错如下:
异常信息
Unable to connect to any of the specified MySQL hosts.
内部异常:序列包含一个以上的匹配元素
测试环境:
win10 1909
Mysql.Data.dll 8.0.18
.Net Framework 4.7
两个现象:
1、奇怪的是微酷家里电脑报错,同样的代码办公室电脑不报错正常连接。
2、异常出现在数据库服务器地址
配置为域名
的情况下,如果改成 IP
则不会报错。
解决过程
断点调试
通过 dnSpy
断点调试 Mysql.Data.dll
发现:序列包含一个以上的匹配元素
这个错误出现在以下代码段中:
命名空间:MySql.Data.Common.StreamCreator
private static Stream GetTcpStream(MySqlConnectionStringBuilder settings)
{
Task<IPAddress[]> hostAddressesAsync = Dns.GetHostAddressesAsync(settings.Server);
hostAddressesAsync.Wait();
if (hostAddressesAsync.Result == null || hostAddressesAsync.Result.Length == 0)
{
throw new ArgumentException(Resources.InvalidHostNameOrAddress);
}
//下面这行代码异常了,发现 hostAddressesAsync.Result 中有两个相同的IP地址。
IPAddress ipaddress = hostAddressesAsync.Result.SingleOrDefault((IPAddress c) => c.AddressFamily == AddressFamily.InterNetwork);
...
}
异常原因:
调试发现家里电脑经过域名解析后得到两个相同的 IP 地址
,而为何会查到两个重复的 IP 地址呢,不去追了,重点是如上代码中 Linq
的 SingleOrDefault
方法当匹配的元素有多个时
会抛出 InvalidOperationException
异常,即:序列包含一个以上的匹配元素
。
解决方案
经过上面调试确定是在通过域名获取 IP 地址时因为返回了多个 IP 而抛出异常
。
官方有个讨论贴:https://bugs.mysql.com/bug.php?id=97448
贴中有 3 点值得关注:
8.0.17
没有问题,升级到8.0.18
和8.0.19
均会出现此问题,所以降级到 8.0.17
是个方案。- 官方开发人员说
8.0.20
会修改这个问题,多个 IP 会返回第一个。不过目前(2020/03/20
)8.0.20 未发布。 - 有人提可更换为
MySqlConnector 0.61.0
,它不仅具有相同的 api 并且性能提升不少。
另外:
微酷的方案是在自己项目代码中将主机名解析为 IP 后取第 1 个替换到数据库连接字符串的 server 中。
C# 版:
var ips = System.Net.Dns.GetHostAddresses("数据库服务器域名www.weiku.co");
var ip = ips.FirstOrDefault(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
string connString = $"Server={ip};Port=3306;Database=weiku;Uid=weiku;Pwd=123";
/// <summary>
/// 获取主机名解析后的第一个外网 IP
/// 用于应对 MySQL.Data 8.0.18 对于一个域名解析到多个 IP 抛出异常问题。
/// 详情:https://weiku.co/article/559/
/// </summary>
/// <param name="hostName">主机名,如:weiku.co</param>
/// <returns>第一个外网 IP</returns>
public static string GetDNSIP(string hostName)
{
try
{
var ip = System.Net.Dns.GetHostAddresses(hostName)
.FirstOrDefault(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
return ip?.ToString();
}
catch (Exception ex)
{
throw new Exception("提供的主机地址解析 IP 出错。" + ex.Message);
}
}