cachedsql.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package sqlc
  2. import (
  3. "database/sql"
  4. "log"
  5. "time"
  6. "github.com/tal-tech/go-zero/core/stores/cache"
  7. "github.com/tal-tech/go-zero/core/stores/redis"
  8. "github.com/tal-tech/go-zero/core/stores/sqlx"
  9. "github.com/tal-tech/go-zero/core/syncx"
  10. )
  11. // see doc/sql-cache.md
  12. const cacheSafeGapBetweenIndexAndPrimary = time.Second * 5
  13. var (
  14. // ErrNotFound is an alias of sqlx.ErrNotFound.
  15. ErrNotFound = sqlx.ErrNotFound
  16. // can't use one SingleFlight per conn, because multiple conns may share the same cache key.
  17. exclusiveCalls = syncx.NewSingleFlight()
  18. stats = cache.NewStat("sqlc")
  19. )
  20. type (
  21. // ExecFn defines the sql exec method.
  22. ExecFn func(conn sqlx.SqlConn) (sql.Result, error)
  23. // IndexQueryFn defines the query method that based on unique indexes.
  24. IndexQueryFn func(conn sqlx.SqlConn, v interface{}) (interface{}, error)
  25. // PrimaryQueryFn defines the query method that based on primary keys.
  26. PrimaryQueryFn func(conn sqlx.SqlConn, v, primary interface{}) error
  27. // QueryFn defines the query method.
  28. QueryFn func(conn sqlx.SqlConn, v interface{}) error
  29. // A CachedConn is a DB connection with cache capability.
  30. CachedConn struct {
  31. db sqlx.SqlConn
  32. cache cache.Cache
  33. }
  34. )
  35. // NewNodeConn returns a CachedConn with a redis node cache.
  36. func NewNodeConn(db sqlx.SqlConn, rds *redis.Redis, opts ...cache.Option) CachedConn {
  37. return CachedConn{
  38. db: db,
  39. cache: cache.NewNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...),
  40. }
  41. }
  42. // NewConn returns a CachedConn with a redis cluster cache.
  43. func NewConn(db sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) CachedConn {
  44. return CachedConn{
  45. db: db,
  46. cache: cache.New(c, exclusiveCalls, stats, sql.ErrNoRows, opts...),
  47. }
  48. }
  49. // NewConnWithCache returns a CachedConn with a custom cache.
  50. func NewConnWithCache(db sqlx.SqlConn, c cache.Cache) CachedConn {
  51. if c == nil {
  52. log.Fatal("Invalid cache component")
  53. }
  54. return CachedConn{
  55. db: db,
  56. cache: c,
  57. }
  58. }
  59. // DelCache deletes cache with keys.
  60. func (cc CachedConn) DelCache(keys ...string) error {
  61. return cc.cache.Del(keys...)
  62. }
  63. // GetCache unmarshals cache with given key into v.
  64. func (cc CachedConn) GetCache(key string, v interface{}) error {
  65. return cc.cache.Get(key, v)
  66. }
  67. // Exec runs given exec on given keys, and returns execution result.
  68. func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
  69. res, err := exec(cc.db)
  70. if err != nil {
  71. return nil, err
  72. }
  73. if err := cc.DelCache(keys...); err != nil {
  74. return nil, err
  75. }
  76. return res, nil
  77. }
  78. // ExecNoCache runs exec with given sql statement, without affecting cache.
  79. func (cc CachedConn) ExecNoCache(q string, args ...interface{}) (sql.Result, error) {
  80. return cc.db.Exec(q, args...)
  81. }
  82. // QueryRow unmarshals into v with given key and query func.
  83. func (cc CachedConn) QueryRow(v interface{}, key string, query QueryFn) error {
  84. return cc.cache.Take(v, key, func(v interface{}) error {
  85. return query(cc.db, v)
  86. })
  87. }
  88. // QueryRowIndex unmarshals into v with given key.
  89. func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary interface{}) string,
  90. indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
  91. var primaryKey interface{}
  92. var found bool
  93. if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
  94. primaryKey, err = indexQuery(cc.db, v)
  95. if err != nil {
  96. return
  97. }
  98. found = true
  99. return cc.cache.SetWithExpire(keyer(primaryKey), v, expire+cacheSafeGapBetweenIndexAndPrimary)
  100. }); err != nil {
  101. return err
  102. }
  103. if found {
  104. return nil
  105. }
  106. return cc.cache.Take(v, keyer(primaryKey), func(v interface{}) error {
  107. return primaryQuery(cc.db, v, primaryKey)
  108. })
  109. }
  110. // QueryRowNoCache unmarshals into v with given statement.
  111. func (cc CachedConn) QueryRowNoCache(v interface{}, q string, args ...interface{}) error {
  112. return cc.db.QueryRow(v, q, args...)
  113. }
  114. // QueryRowsNoCache unmarshals into v with given statement.
  115. // It doesn't use cache, because it might cause consistency problem.
  116. func (cc CachedConn) QueryRowsNoCache(v interface{}, q string, args ...interface{}) error {
  117. return cc.db.QueryRows(v, q, args...)
  118. }
  119. // SetCache sets v into cache with given key.
  120. func (cc CachedConn) SetCache(key string, v interface{}) error {
  121. return cc.cache.Set(key, v)
  122. }
  123. // Transact runs given fn in transaction mode.
  124. func (cc CachedConn) Transact(fn func(sqlx.Session) error) error {
  125. return cc.db.Transact(fn)
  126. }