是否必须回收应用池刷新缓存查询计划?有无其他刷新方式?
我之前也碰到过好几次这种糟心的问题,本质上这是ADO.NET(System.Data.SqlClient)的结果集元数据缓存在搞鬼。当你的应用第一次执行存储过程时,.NET会缓存这个存储过程返回的结果集的列类型、顺序等元数据,后续调用会直接复用这个缓存。但你修改字段后,coalesce(r.Price, i.Price)会自动返回优先级更高的decimal类型(比money优先级高),导致缓存的元数据和实际返回的不匹配,触发了类型转换错误。
不用回收应用池的解决方法
以下是几个可行方案,按推荐程度排序:
从SQL层面固定返回字段类型(最稳妥)
既然已经明确问题根源,直接在存储过程里把coalesce的结果强制转成money类型,让结果集的元数据和之前保持一致:Price = CAST(coalesce(r.Price, i.Price) AS MONEY)这样不管后续怎么调整逻辑,返回的字段类型都不会变,从根源上避免了缓存不匹配的问题,完全不需要动应用端的缓存。
刷新ADO.NET的连接池缓存
ADO.NET的元数据缓存和连接池绑定,你可以通过代码手动清除指定连接的连接池,或者所有连接池:- 清除特定连接的连接池:
using (var conn = new SqlConnection("你的数据库连接字符串")) { conn.Open(); SqlConnection.ClearPool(conn); } - 清除所有连接池:
SqlConnection.ClearAllPools();
执行后,新的数据库连接会重新获取存储过程的元数据,不会再复用旧缓存。不过生产环境执行
ClearAllPools可能会短暂影响性能,建议在低峰期操作。- 清除特定连接的连接池:
临时修改存储过程名称(应急方案)
如果你暂时不想动代码或SQL逻辑,可以临时把存储过程重命名(比如从GetProductPrice改成GetProductPrice_Updated),然后修改应用里调用的存储过程名称。因为.NET是按「存储过程名称+连接字符串」来缓存元数据的,名称变更后会重新缓存新的元数据。不过这个方法比较hack,不推荐长期使用,仅适合应急。
只能回收应用池吗?
当然不是!上面的方法都可以不用回收应用池就解决问题。回收应用池是最粗暴的方式——它会清空应用的所有内存缓存(包括ADO.NET的元数据缓存),但代价是应用会有短暂的服务中断,用户可能碰到请求失败的情况,所以尽量优先用前面更轻量的方案。
内容的提问来源于stack exchange,提问作者BVernon

