transaction.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. package core
  2. import (
  3. "TsDB2/DB/core/page"
  4. "encoding/binary"
  5. "fmt"
  6. )
  7. // Transaction 事务管理器
  8. //
  9. // 核心思想:
  10. // - 事务开始时,记录所有被修改行的原始数据和新数据
  11. // - 事务内操作正常修改 BufferPool
  12. // - Commit 时将行级数据写入 WAL(节省空间)
  13. // - Rollback 时用原始数据覆盖 BufferPool 中的脏页(立即生效)
  14. type Transaction struct {
  15. db *DB
  16. originalPages map[uint32][]byte // pageID → 原始页数据快照(用于 Rollback)
  17. rowChanges map[uint32]struct{ original, new []byte } // pageID → {原始行, 新行}(用于 WAL)
  18. committed bool
  19. rolledBack bool
  20. }
  21. // BeginTransaction 开启新事务
  22. func (db *DB) BeginTransaction() *Transaction {
  23. txn := &Transaction{
  24. db: db,
  25. originalPages: make(map[uint32][]byte),
  26. rowChanges: make(map[uint32]struct{ original, new []byte }),
  27. committed: false,
  28. rolledBack: false,
  29. }
  30. // 设置当前活跃事务
  31. db.currentTxn = txn
  32. return txn
  33. }
  34. // cacheOriginalPage 缓存页的原始数据(首次修改时调用)
  35. func (txn *Transaction) cacheOriginalPage(pageID uint32) error {
  36. // 如果已经缓存过,不需要重复缓存
  37. if _, exists := txn.originalPages[pageID]; exists {
  38. return nil
  39. }
  40. // 从磁盘读取原始数据
  41. buf := make([]byte, PageSize)
  42. if err := txn.db.file.readPageDisk(pageID, buf); err != nil {
  43. return fmt.Errorf("failed to read page %d for rollback cache: %v", pageID, err)
  44. }
  45. txn.originalPages[pageID] = buf
  46. return nil
  47. }
  48. // RecordRowChange 记录行级变化(用于 WAL)
  49. // originalRow: 原始行数据
  50. // newRow: 新行数据
  51. func (txn *Transaction) RecordRowChange(pageID uint32, originalRow []byte, newRow []byte) {
  52. txn.rowChanges[pageID] = struct{ original, new []byte }{
  53. original: originalRow,
  54. new: newRow,
  55. }
  56. }
  57. // Commit 提交事务
  58. // 混合 WAL 策略:
  59. // 1. 整页条目: 恢复所有被修改页的结构 (B+树节点, IndexMeta, HeaderPage, DataPage)
  60. // 2. 行级条目: 补充 DataPage 的行数据变更 (用于崩溃恢复时精确重做)
  61. //
  62. // 恢复时先应用整页条目 (Phase 1), 再应用行级条目 (Phase 2)
  63. func (txn *Transaction) Commit() error {
  64. if txn.committed {
  65. return fmt.Errorf("transaction already committed")
  66. }
  67. if txn.rolledBack {
  68. return fmt.Errorf("transaction already rolled back")
  69. }
  70. // 1. 收集 BufferPool 脏页
  71. dirty := txn.db.file.CollectDirty()
  72. if len(dirty) == 0 && len(txn.originalPages) == 0 {
  73. txn.committed = true
  74. txn.db.currentTxn = nil
  75. txn.originalPages = nil
  76. txn.rowChanges = nil
  77. return nil
  78. }
  79. // 2. 创建 WAL 事务
  80. wal := txn.db.BeginTxn()
  81. // 3. 写整页条目: 所有被修改的页 (保证页结构完整恢复)
  82. // 优先用 BufferPool 脏页 (最新数据), 补充 originalPages 中的页
  83. dirtySet := make(map[uint32]bool)
  84. for _, d := range dirty {
  85. wal.RecordPage(d.PageID, d.Data)
  86. dirtySet[d.PageID] = true
  87. }
  88. for pid, origData := range txn.originalPages {
  89. if !dirtySet[pid] {
  90. // 原始页被修改但不在脏页中 (理论上不会发生, 安全补充)
  91. wal.RecordPage(pid, origData)
  92. }
  93. }
  94. // 4. HeaderPage 也纳入
  95. wal.RecordPage(0, marshalHeader(txn.db.header))
  96. // 5. 写行级条目: 对 DataPage 提取行差异 (补充恢复信息)
  97. for _, d := range dirty {
  98. origData, hasOrig := txn.originalPages[d.PageID]
  99. if !hasOrig {
  100. continue // 新页无原始版本, 跳过行差异
  101. }
  102. // 只处理 DataPage (type=2)
  103. if len(origData) < 18 || origData[0] != byte(page.DataIndex) {
  104. continue
  105. }
  106. rowEntries := diffDataPages(origData, d.Data)
  107. for _, re := range rowEntries {
  108. wal.RecordRowChange(re.PageID, re.OpType, re.StoreRow)
  109. }
  110. }
  111. // 6. ⭐ 关键:Commit 会触发 fsync,确保落盘 ⭐
  112. if err := wal.Commit(); err != nil {
  113. return fmt.Errorf("WAL commit failed: %v", err)
  114. }
  115. // 7. 清理事务状态 + 释放原始数据快照
  116. txn.committed = true
  117. txn.db.currentTxn = nil
  118. txn.originalPages = nil
  119. txn.rowChanges = nil
  120. return nil
  121. }
  122. // =============================================================================
  123. // diffDataPages 比较两个 DataPage 缓冲区, 提取行级变更
  124. // 返回 INSERT/DELETE/UPDATE 的 RowEntry 列表
  125. // =============================================================================
  126. func diffDataPages(origBuf, curBuf []byte) []RowEntry {
  127. const hdrSize = 38 // PageHdr(18) + Count(4) + Min(8) + Max(8)
  128. const delFlag = uint32(0x80000000)
  129. type rowInfo struct {
  130. storeRow []byte
  131. deleted bool
  132. }
  133. // 解析 DataPage 中所有行
  134. parseRows := func(buf []byte) map[uint64]rowInfo {
  135. rows := make(map[uint64]rowInfo)
  136. if len(buf) < hdrSize {
  137. return rows
  138. }
  139. count := binary.LittleEndian.Uint32(buf[18:])
  140. dataEnd := binary.LittleEndian.Uint32(buf[0:]) // UseSize (offset 14, but we use PageHdr layout)
  141. // UseSize is at offset 14 in PageHdr: Type(1) + _(3) + PageID(4) + PrePage(4) + NextPage(4) + UseSize(4)
  142. dataEnd = binary.LittleEndian.Uint32(buf[14:])
  143. off := uint32(hdrSize)
  144. for i := uint32(0); i < count; i++ {
  145. if off+4 > dataEnd || off+4 > uint32(len(buf)) {
  146. break
  147. }
  148. rawLen := binary.LittleEndian.Uint32(buf[off:])
  149. deleted := rawLen&delFlag != 0
  150. rowLen := rawLen & ^delFlag
  151. rowStart := off + 4
  152. if rowStart+rowLen > dataEnd || rowStart+rowLen > uint32(len(buf)) {
  153. break
  154. }
  155. if rowLen >= 8 {
  156. pkID := binary.BigEndian.Uint64(buf[rowStart : rowStart+8])
  157. row := make([]byte, rowLen)
  158. copy(row, buf[rowStart:rowStart+rowLen])
  159. rows[pkID] = rowInfo{storeRow: row, deleted: deleted}
  160. }
  161. off = rowStart + rowLen
  162. }
  163. return rows
  164. }
  165. origRows := parseRows(origBuf)
  166. curRows := parseRows(curBuf)
  167. var result []RowEntry
  168. // 检测 DELETE 和 UPDATE (存在于原始页中的行)
  169. for pkID, orig := range origRows {
  170. if orig.deleted {
  171. continue // 原始页中已删除的行不处理
  172. }
  173. cur, exists := curRows[pkID]
  174. if !exists || cur.deleted {
  175. // 行被删除
  176. pkBuf := make([]byte, 8)
  177. binary.BigEndian.PutUint64(pkBuf, pkID)
  178. result = append(result, RowEntry{OpType: WALOpDelete, StoreRow: pkBuf})
  179. } else {
  180. // 检查行数据是否变化
  181. if !bytesEqual(orig.storeRow, cur.storeRow) {
  182. result = append(result, RowEntry{OpType: WALOpUpdate, StoreRow: cur.storeRow})
  183. }
  184. }
  185. }
  186. // 检测 INSERT (存在于当前页但不在原始页中的行)
  187. for pkID, cur := range curRows {
  188. if cur.deleted {
  189. continue
  190. }
  191. if _, exists := origRows[pkID]; !exists {
  192. result = append(result, RowEntry{OpType: WALOpInsert, StoreRow: cur.storeRow})
  193. }
  194. }
  195. return result
  196. }
  197. func bytesEqual(a, b []byte) bool {
  198. if len(a) != len(b) {
  199. return false
  200. }
  201. for i := range a {
  202. if a[i] != b[i] {
  203. return false
  204. }
  205. }
  206. return true
  207. }
  208. // Rollback 回滚事务
  209. // 用原始数据快照覆盖 BufferPool 中的脏页(立即生效)
  210. // 回滚后清理所有缓存数据(释放内存)
  211. func (txn *Transaction) Rollback() error {
  212. if txn.committed {
  213. return fmt.Errorf("transaction already committed, cannot rollback")
  214. }
  215. if txn.rolledBack {
  216. return fmt.Errorf("transaction already rolled back")
  217. }
  218. // 用原始数据覆盖所有被修改的页
  219. for pageID, originalData := range txn.originalPages {
  220. // 直接写回 BufferPool(绕过 WAL)
  221. if err := txn.db.file.WritePage(pageID, originalData); err != nil {
  222. return fmt.Errorf("failed to rollback page %d: %v", pageID, err)
  223. }
  224. }
  225. // 清理事务状态 + 释放所有缓存数据
  226. txn.rolledBack = true
  227. txn.db.currentTxn = nil
  228. txn.originalPages = nil // ← 回滚成功,原始数据和修改数据都不再需要
  229. txn.rowChanges = nil // ← 清理行级变化
  230. return nil
  231. }
  232. // IsCommitted 检查事务是否已提交
  233. func (txn *Transaction) IsCommitted() bool {
  234. return txn.committed
  235. }
  236. // IsRolledBack 检查事务是否已回滚
  237. func (txn *Transaction) IsRolledBack() bool {
  238. return txn.rolledBack
  239. }