diff --git a/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs b/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs index 02eeb6ed..f4784bb2 100644 --- a/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs +++ b/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs @@ -101,5 +101,37 @@ namespace YLErp.Modules.DataProviderModule Assert.AreEqual(1, _loadCount, "稳态窗口内多次读取零重载(零查询)"); } + + [TestMethod] + public void TTL过期重载失败_保留旧快照不抛_且窗口内不重试() + { + Fr007FixingCache.NowTicks = () => 1_000_000L; + Assert.IsTrue(Fr007FixingCache.TryGet(D1, out _), "先成功装载一次"); + Assert.AreEqual(1, _loadCount); + + Fr007FixingCache.LoadSnapshot = () => { _loadCount++; throw new InvalidOperationException("db down"); }; + Fr007FixingCache.NowTicks = () => 1_000_000L + TimeSpan.FromMinutes(1).Ticks + 1; // TTL 过期 → 触发重载 → 失败 + + // 不抛:历史定盘不可变,命中继续走旧快照(异常会直接 fail 本用例) + Assert.IsTrue(Fr007FixingCache.TryGet(D1, out var p), "重载失败应保留旧快照继续命中"); + Assert.AreEqual(0.0142, p, 1e-12, "旧快照值不变"); + Assert.AreEqual(2, _loadCount, "失败的重载尝试恰好一次"); + + Fr007FixingCache.TryGet(D2, out _); // TTL 时钟已被重置:窗口内不再重试(防重试风暴) + Assert.AreEqual(2, _loadCount, "失败后TTL窗口内不得反复重试全表SELECT"); + } + + [TestMethod] + public void 首次装载失败_miss不抛_窗口内不重试() + { + Fr007FixingCache.LoadSnapshot = () => { _loadCount++; throw new InvalidOperationException("db down"); }; + Fr007FixingCache.NowTicks = () => 1_000_000L; + + Assert.IsFalse(Fr007FixingCache.TryGet(D1, out _), "装载失败=空快照miss(调用方直查库兜底,新数据该响仍响)"); + Assert.AreEqual(1, _loadCount); + + Fr007FixingCache.TryGet(D1, out _); + Assert.AreEqual(1, _loadCount, "失败后TTL窗口内不重试"); + } } } diff --git a/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs b/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs index c8a73b43..23e1dd26 100644 --- a/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs +++ b/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs @@ -156,7 +156,7 @@ namespace YLErp.Modules.DataProviderModule return true; using var db = DbContextFactory.GetYLDbContext(); - var data = db.eod_commodity_future_price.Where(x => x.ValueDate == valueDate && x.UnderlyingCode == underlyingCode).OrderByDescending(o => o.ValueDate).FirstOrDefault(); + var data = db.eod_commodity_future_price.Where(x => x.ValueDate == valueDate && x.UnderlyingCode == underlyingCode).FirstOrDefault(); if (data != null) { @@ -164,6 +164,11 @@ namespace YLErp.Modules.DataProviderModule // 利息腿计算直接作为 floatRate 参与 principal*(fixedRate+floatRate)/annualDays,无需再 ÷100。 price = data.ReferencePrice ?? 0; + // 快照漏掉的行(当日新发布/补录历史)直查命中——失效快照,下次访问整表重载后回到 O(1), + // 否则该键在 TTL 窗口内每次读取都退化为点查。 + if (underlyingCode == Fr007FixingCache.UnderlyingCode) + Fr007FixingCache.Invalidate(); + return true; } diff --git a/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs b/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs index 16d6ef20..8fa0d85f 100644 --- a/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs +++ b/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs @@ -80,11 +80,24 @@ namespace YLErp.Modules.DataProviderModule lock (_reloadLock) { if (IsFresh()) return; - var snap = LoadSnapshot(); - Volatile.Write(ref _snapshot, snap); - Volatile.Write(ref _loaded, true); - Volatile.Write(ref _loadedAt, NowTicks()); - Volatile.Write(ref _snapVersion, Volatile.Read(ref _writeVersion)); + try + { + var snap = LoadSnapshot(); + Volatile.Write(ref _snapshot, snap); + Volatile.Write(ref _loaded, true); + Volatile.Write(ref _loadedAt, NowTicks()); + Volatile.Write(ref _snapVersion, Volatile.Read(ref _writeVersion)); + } + catch (Exception ex) + { + // 重载失败(如DB抖动):保留旧快照并重置TTL时钟——历史定盘不可变,旧值依旧正确; + // 同时防止每次读取都重试全表SELECT(重试风暴)。不抛:命中继续走旧快照, + // miss照旧回退直查库,新数据取不到仍会在直查处响亮报错。 + // 注:版本失效(Invalidate)后的重载失败不会被TTL掩盖——版本不相等会持续重试直到成功。 + Volatile.Write(ref _loaded, true); + Volatile.Write(ref _loadedAt, NowTicks()); + LogFactory.GetLogger("Fr007FixingCache").Error("FR007定盘缓存重载失败,沿用旧快照", ex); + } } }