6
6
"github.com/asjdf/gorm-cache/config"
7
7
"github.com/asjdf/gorm-cache/storage"
8
8
"github.com/asjdf/gorm-cache/util"
9
+ "github.com/hashicorp/go-multierror"
9
10
"gorm.io/gorm"
10
11
"gorm.io/gorm/callbacks"
11
12
"reflect"
@@ -56,8 +57,17 @@ func (h *queryHandler) BeforeQuery() func(db *gorm.DB) {
56
57
db .InstanceSet ("gorm:cache:vars" , db .Statement .Vars )
57
58
58
59
if util .ShouldCache (tableName , cache .Config .Tables ) {
60
+ hitted := false
61
+ defer func () {
62
+ if hitted {
63
+ cache .IncrHitCount ()
64
+ } else {
65
+ cache .IncrMissCount ()
66
+ }
67
+ }()
68
+
59
69
// singleFlight Check
60
- singleFlightKey := fmt .Sprintf ("%s:%s" , tableName , sql )
70
+ singleFlightKey := fmt .Sprintf ("%s:%s" , tableName , sql ) // todo: key with vars
61
71
h .singleFlight .mu .Lock ()
62
72
if h .singleFlight .m == nil {
63
73
h .singleFlight .m = make (map [string ]* call )
@@ -78,11 +88,11 @@ func (h *queryHandler) BeforeQuery() func(db *gorm.DB) {
78
88
_ = db .AddError (err )
79
89
return
80
90
}
91
+ hitted = true
81
92
db .RowsAffected = c .rowsAffected
82
- if c .err == nil { // 为保证后续流程不走,必须设一个error
83
- db .Error = util .SingleFlightHit
84
- } else {
85
- db .Error = c .err
93
+ db .Error = multierror .Append (util .SingleFlightHit ) // 为保证后续流程不走,必须设一个error
94
+ if c .err != nil {
95
+ db .Error = multierror .Append (db .Error , c .err )
86
96
}
87
97
h .cache .Logger .CtxInfo (ctx , "[BeforeQuery] single flight hit for key %v" , singleFlightKey )
88
98
return
@@ -93,8 +103,7 @@ func (h *queryHandler) BeforeQuery() func(db *gorm.DB) {
93
103
h .singleFlight .mu .Unlock ()
94
104
db .InstanceSet ("gorm:cache:query:single_flight_call" , c )
95
105
96
- // try primary cache first
97
- if cache .Config .CacheLevel == config .CacheLevelAll || cache .Config .CacheLevel == config .CacheLevelOnlyPrimary {
106
+ tryPrimaryCache := func () (hitted bool ) {
98
107
primaryKeys := getPrimaryKeysFromWhereClause (db )
99
108
cache .Logger .CtxInfo (ctx , "[BeforeQuery] parse primary keys = %v" , primaryKeys )
100
109
@@ -141,17 +150,17 @@ func (h *queryHandler) BeforeQuery() func(db *gorm.DB) {
141
150
return
142
151
}
143
152
db .Error = util .PrimaryCacheHit
153
+ hitted = true
144
154
return
145
155
}
146
156
147
- if cache . Config . CacheLevel == config . CacheLevelAll || cache . Config . CacheLevel == config . CacheLevelOnlySearch {
157
+ trySearchCache := func () ( hitted bool ) {
148
158
// search cache hit
149
159
cacheValue , err := cache .GetSearchCache (ctx , tableName , sql , db .Statement .Vars ... )
150
160
if err != nil {
151
161
if ! errors .Is (err , storage .ErrCacheNotFound ) {
152
162
cache .Logger .CtxError (ctx , "[BeforeQuery] get cache value for sql %s error: %v" , sql , err )
153
163
}
154
- cache .IncrMissCount ()
155
164
db .Error = nil
156
165
return
157
166
}
@@ -174,8 +183,21 @@ func (h *queryHandler) BeforeQuery() func(db *gorm.DB) {
174
183
return
175
184
}
176
185
db .Error = util .SearchCacheHit
186
+ hitted = true
177
187
return
178
188
}
189
+
190
+ if cache .Config .CacheLevel == config .CacheLevelAll || cache .Config .CacheLevel == config .CacheLevelOnlyPrimary {
191
+ if tryPrimaryCache () {
192
+ hitted = true
193
+ return
194
+ }
195
+ }
196
+ if cache .Config .CacheLevel == config .CacheLevelAll || cache .Config .CacheLevel == config .CacheLevelOnlySearch {
197
+ if ! hitted && trySearchCache () {
198
+ hitted = true
199
+ }
200
+ }
179
201
}
180
202
}
181
203
}
@@ -196,7 +218,11 @@ func (h *queryHandler) AfterQuery() func(db *gorm.DB) {
196
218
varObj , _ := db .InstanceGet ("gorm:cache:vars" )
197
219
vars := varObj .([]interface {})
198
220
199
- if db .Error == nil && util .ShouldCache (tableName , cache .Config .Tables ) {
221
+ if ! util .ShouldCache (tableName , cache .Config .Tables ) {
222
+ return
223
+ }
224
+
225
+ if db .Error == nil {
200
226
// error is nil -> cache not hit, we cache newly retrieved data
201
227
primaryKeys , objects := getObjectsAfterLoad (db )
202
228
@@ -266,29 +292,45 @@ func (h *queryHandler) AfterQuery() func(db *gorm.DB) {
266
292
return
267
293
}
268
294
269
- if ! cache .Config .DisableCachePenetrationProtect {
270
- if errors .Is (db .Error , gorm .ErrRecordNotFound ) { // 应对缓存穿透 未来可能考虑使用其他过滤器实现:如布隆过滤器
271
- cache .Logger .CtxInfo (ctx , "[AfterQuery] set cache: %v" , "recordNotFound" )
272
- err := cache .SetSearchCache (ctx , "recordNotFound" , tableName , sql , vars ... )
273
- if err != nil {
274
- cache .Logger .CtxError (ctx , "[AfterQuery] set search cache for sql: %s error: %v" , sql , err )
275
- return
276
- }
277
- cache .Logger .CtxInfo (ctx , "[AfterQuery] sql %s cached" , sql )
295
+ // 应对缓存穿透 未来可能考虑使用其他过滤器实现:如布隆过滤器
296
+ if db .Error == gorm .ErrRecordNotFound && ! cache .Config .DisableCachePenetrationProtect {
297
+ cache .Logger .CtxInfo (ctx , "[AfterQuery] set cache: %v" , "recordNotFound" )
298
+ err := cache .SetSearchCache (ctx , "recordNotFound" , tableName , sql , vars ... )
299
+ if err != nil {
300
+ cache .Logger .CtxError (ctx , "[AfterQuery] set search cache for sql: %s error: %v" , sql , err )
278
301
return
279
302
}
280
- }
281
-
282
- switch db .Error {
283
- case util .RecordNotFoundCacheHit :
284
- db .Error = gorm .ErrRecordNotFound
285
- cache .IncrHitCount ()
286
- case util .SearchCacheHit , util .PrimaryCacheHit , util .SingleFlightHit :
287
- db .Error = nil
288
- cache .IncrHitCount ()
303
+ cache .Logger .CtxInfo (ctx , "[AfterQuery] sql %s cached" , sql )
304
+ return
289
305
}
290
306
}()
307
+ // 之所以将上面的部分包在一个匿名函数中是为了方便
308
+ // 上面的cache完成后直接传播给其他等待中的goroutine
309
+ // 上面只处理非singleflight且无错误或记录不存在的情况
291
310
h .fillCallAfterQuery (db )
311
+
312
+ // 下面处理命中了缓存的情况
313
+ // 有以下几种err是专门用来传状态的:正常的cachehit 这种情况不存在error
314
+ // RecordNotFoundCacheHit 这种情况只会在notfound之后出现
315
+ // SingleFlightHit 这种情况下error中除了SingleFlightHit还可能会存在其他error来自gorm的error
316
+ // 且遇到任何一种hit我们都可以认为是命中了缓存 同时只可能命中至多两个hit(single+其他
317
+ if merr , ok := db .Error .(* multierror.Error ); ok {
318
+ errs := merr .WrappedErrors ()
319
+ if errors .Is (errs [0 ], util .SingleFlightHit ) {
320
+ if len (errs ) > 1 {
321
+ db .Error = errs [1 ]
322
+ } else {
323
+ db .Error = nil
324
+ }
325
+ }
326
+ }
327
+
328
+ switch db .Error {
329
+ case util .RecordNotFoundCacheHit :
330
+ db .Error = gorm .ErrRecordNotFound
331
+ case util .SearchCacheHit , util .PrimaryCacheHit :
332
+ db .Error = nil
333
+ }
292
334
}
293
335
}
294
336
0 commit comments