[{"data":1,"prerenderedAt":232},["ShallowReactive",2],{"content:\u002Fprojects\u002Fmydb\u002Fmydb1":3,"surround:\u002Fprojects\u002Fmydb\u002Fmydb1":221},{"id":4,"title":5,"body":6,"categories":198,"date":200,"description":201,"draft":202,"extension":203,"image":204,"meta":205,"navigation":207,"path":208,"permalink":204,"published":204,"readingTime":209,"recommend":204,"references":204,"seo":214,"sitemap":215,"stem":216,"tags":217,"type":219,"__hash__":220},"content\u002Fposts\u002Fprojects\u002Fmydb\u002Fmydb1.md","MYDB 1. 从最简单的 TM 开始",{"type":7,"value":8,"toc":192},"minimark",[9,21,24,30,35,38,41,44,57,60,63,74,77,80,86,89,92,98,101,104,110,116,122,133,147,161,167,170,180,186,189],[10,11,12,13,20],"p",{},"本章涉及代码都在 ",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fgithub.com\u002FCN-GuoZiyang\u002FMYDB\u002Ftree\u002Fmaster\u002Fsrc\u002Fmain\u002Fjava\u002Ftop\u002Fguoziyang\u002Fmydb\u002Fbackend\u002Ftm",[18],"nofollow","backend\u002Ftm"," 中。",[10,22,23],{},"如第 0 章所述：",[25,26,27],"blockquote",{},[10,28,29],{},"TM 通过维护 XID 文件来维护事务的状态，并提供接口供其他模块来查询某个事务的状态。",[31,32,34],"h3",{"id":33},"xid-文件","XID 文件",[10,36,37],{},"下面主要是规则的定义了。",[10,39,40],{},"在 MYDB 中，每一个事务都有一个 XID，这个 ID 唯一标识了这个事务。事务的 XID 从 1 开始标号，并自增，不可重复。并特殊规定 XID 0 是一个超级事务（Super Transaction）。当一些操作想在没有申请事务的情况下进行，那么可以将操作的 XID 设置为 0。XID 为 0 的事务的状态永远是 committed。",[10,42,43],{},"TransactionManager 维护了一个 XID 格式的文件，用来记录各个事务的状态。MYDB 中，每个事务都有下面的三种状态：",[45,46,47,51,54],"ol",{},[48,49,50],"li",{},"active，正在进行，尚未结束",[48,52,53],{},"committed，已提交",[48,55,56],{},"aborted，已撤销（回滚）",[10,58,59],{},"XID 文件给每个事务分配了一个字节的空间，用来保存其状态。同时，在 XID 文件的头部，还保存了一个 8 字节的数字，记录了这个 XID 文件管理的事务的个数。于是，事务 xid 在文件中的状态就存储在 (xid-1)+8 字节处，xid-1 是因为 xid 0（Super XID）的状态不需要记录。",[10,61,62],{},"TransactionManager 提供了一些接口供其他模块调用，用来创建事务和查询事务状态。更具体的：",[64,65,71],"pre",{"className":66,"code":68,"language":69,"meta":70},[67],"language-java","public interface TransactionManager {\n    long begin();                       \u002F\u002F 开启一个新事务\n    void commit(long xid);              \u002F\u002F 提交一个事务\n    void abort(long xid);               \u002F\u002F 取消一个事务\n    boolean isActive(long xid);         \u002F\u002F 查询一个事务的状态是否是正在进行的状态\n    boolean isCommitted(long xid);      \u002F\u002F 查询一个事务的状态是否是已提交\n    boolean isAborted(long xid);        \u002F\u002F 查询一个事务的状态是否是已取消\n    void close();                       \u002F\u002F 关闭 TM\n}\n","java","",[72,73,68],"code",{"__ignoreMap":70},[31,75,76],{"id":76},"实现",[10,78,79],{},"规则很简单，剩下的就是编码了。首先定义一些必要的常量：",[64,81,84],{"className":82,"code":83,"language":69,"meta":70},[67],"\u002F\u002F XID 文件头长度\nstatic final int LEN_XID_HEADER_LENGTH = 8;\n\u002F\u002F 每个事务的占用长度\nprivate static final int XID_FIELD_SIZE = 1;\n\u002F\u002F 事务的三种状态\nprivate static final byte FIELD_TRAN_ACTIVE   = 0;\nprivate static final byte FIELD_TRAN_COMMITTED = 1;\nprivate static final byte FIELD_TRAN_ABORTED  = 2;\n\u002F\u002F 超级事务，永远为 commited 状态\npublic static final long SUPER_XID = 0;\n\u002F\u002F XID 文件后缀\nstatic final String XID_SUFFIX = \".xid\";\n",[72,85,83],{"__ignoreMap":70},[10,87,88],{},"文件读写都采用了 NIO 方式的 FileChannel，读写方式都和传统 IO 的 Input\u002FOutput Stream 都有一些区别，不过区别主要是接口方面，熟悉使用即可。",[10,90,91],{},"在构造函数创建了一个 TransactionManager 之后，首先要对 XID 文件进行校验，以保证这是一个合法的 XID 文件。校验的方式也很简单，通过文件头的 8 字节数字反推文件的理论长度，与文件的实际长度做对比。如果不同则认为 XID 文件不合法。",[64,93,96],{"className":94,"code":95,"language":69,"meta":70},[67],"private void checkXIDCounter() {\n    long fileLen = 0;\n    try {\n        fileLen = file.length();\n    } catch (IOException e1) {\n        Panic.panic(Error.BadXIDFileException);\n    }\n    if(fileLen \u003C LEN_XID_HEADER_LENGTH) {\n        Panic.panic(Error.BadXIDFileException);\n    }\n\n    ByteBuffer buf = ByteBuffer.allocate(LEN_XID_HEADER_LENGTH);\n    try {\n        fc.position(0);\n        fc.read(buf);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n    this.xidCounter = Parser.parseLong(buf.array());\n    long end = getXidPosition(this.xidCounter + 1);\n    if(end != fileLen) {\n        Panic.panic(Error.BadXIDFileException);\n    }\n}\n",[72,97,95],{"__ignoreMap":70},[10,99,100],{},"对于校验没有通过的，会直接通过 panic 方法，强制停机。在一些基础模块中出现错误都会如此处理，无法恢复的错误只能直接停机。",[10,102,103],{},"我们可以先写一个小的方法，用来获取 xid 的状态在文件中的偏移：",[64,105,108],{"className":106,"code":107,"language":69,"meta":70},[67],"\u002F\u002F 根据事务 xid 取得其在 xid 文件中对应的位置\nprivate long getXidPosition(long xid) {\n    return LEN_XID_HEADER_LENGTH + (xid-1)*XID_FIELD_SIZE;\n}\n",[72,109,107],{"__ignoreMap":70},[10,111,112,115],{},[72,113,114],{"code":114},"begin()"," 方法会开始一个事务，更具体的，首先设置 xidCounter+1 事务的状态为 active，随后 xidCounter 自增，并更新文件头。",[64,117,120],{"className":118,"code":119,"language":69,"meta":70},[67],"\u002F\u002F 开始一个事务，并返回 XID\npublic long begin() {\n    counterLock.lock();\n    try {\n        long xid = xidCounter + 1;\n        updateXID(xid, FIELD_TRAN_ACTIVE);\n        incrXIDCounter();\n        return xid;\n    } finally {\n        counterLock.unlock();\n    }\n}\n\n\u002F\u002F 更新 xid 事务的状态为 status\nprivate void updateXID(long xid, byte status) {\n    long offset = getXidPosition(xid);\n    byte[] tmp = new byte[XID_FIELD_SIZE];\n    tmp[0] = status;\n    ByteBuffer buf = ByteBuffer.wrap(tmp);\n    try {\n        fc.position(offset);\n        fc.write(buf);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n    try {\n        fc.force(false);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n}\n\n\u002F\u002F 将 XID 加一，并更新 XID Header\nprivate void incrXIDCounter() {\n    xidCounter ++;\n    ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter));\n    try {\n        fc.position(0);\n        fc.write(buf);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n    try {\n        fc.force(false);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n}\n",[72,121,119],{"__ignoreMap":70},[10,123,124,125,128,129,132],{},"注意，这里的所有文件操作，在执行后都需要立刻刷入文件中，防止在崩溃后文件丢失数据，fileChannel 的 ",[72,126,127],{"code":127},"force()"," 方法，强制同步缓存内容到文件中，类似于 BIO 中的 ",[72,130,131],{"code":131},"flush()"," 方法。force 方法的参数是一个布尔，表示是否同步文件的元数据（例如最后修改时间等）。",[10,134,135,138,139,142,143,146],{},[72,136,137],{"code":137},"commit()"," 和 ",[72,140,141],{"code":141},"abort()"," 方法就可以直接借助 ",[72,144,145],{"code":145},"updateXID()"," 方法实现。",[10,148,149,150,153,154,138,157,160],{},"同样，",[72,151,152],{"code":152},"isActive()","、",[72,155,156],{"code":156},"isCommitted()",[72,158,159],{"code":159},"isAborted()"," 都是检查一个 xid 的状态，可以用一个通用的方法解决：",[64,162,165],{"className":163,"code":164,"language":69,"meta":70},[67],"\u002F\u002F 检测 XID 事务是否处于 status 状态\nprivate boolean checkXID(long xid, byte status) {\n    long offset = getXidPosition(xid);\n    ByteBuffer buf = ByteBuffer.wrap(new byte[XID_FIELD_SIZE]);\n    try {\n        fc.position(offset);\n        fc.read(buf);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n    return buf.array()[0] == status;\n}\n",[72,166,164],{"__ignoreMap":70},[10,168,169],{},"当然，检查之间记得排除 SUPER_XID。",[10,171,172,173,138,176,179],{},"另外就是两个静态方法：",[72,174,175],{"code":175},"create()",[72,177,178],{"code":178},"open()","，分别表示创建一个 xid 文件并创建 TM 和从一个已有的 xid 文件来创建 TM。从零创建 XID 文件时需要写一个空的 XID 文件头，即设置 xidCounter 为 0，否则后续在校验时会不合法：",[64,181,184],{"className":182,"code":183,"language":69,"meta":70},[67],"public static TransactionManagerImpl create(String path) {\n    ...\n    \u002F\u002F 写空 XID 文件头\n    ByteBuffer buf = ByteBuffer.wrap(new byte[TransactionManagerImpl.LEN_XID_HEADER_LENGTH]);\n    try {\n        fc.position(0);\n        fc.write(buf);\n    } catch (IOException e) {\n        Panic.panic(e);\n    }\n    ...\n}\n",[72,185,183],{"__ignoreMap":70},[10,187,188],{},"TM 就这么结束了，看起来也没什么难的嘛（￣ c￣）y-～",[10,190,191],{},"不着急，真正困难的 DM 还在后面，那可不是一章就能讲完的了~",{"title":70,"searchDepth":193,"depth":193,"links":194},4,[195,197],{"id":33,"depth":196,"text":34},3,{"id":76,"depth":196,"text":76},[199],"项目","2021-11-28 16:10:00","MYDB 中，事务的管理是通过 XID 文件实现的，每个事务都有一个唯一的 XID，从 1 开始递增，XID 0 被定义为超级事务，状态始终为已提交。TransactionManager 负责维护这一文件，并记录事务的三种状态：活动、已提交和已中止。这一机制确保了事务的状态能够被准确查询和管理，为系统的稳定性和可靠性提供了基础。",false,"md",null,{"slots":206},{},true,"\u002Fprojects\u002Fmydb\u002Fmydb1",{"text":210,"minutes":211,"time":212,"words":213},"7 min read",6.885,413100,1377,{"title":5,"description":201},{"loc":208},"posts\u002Fprojects\u002Fmydb\u002Fmydb1",[69,218],"mydb","tech","jxLS72yaLC4iWucVw8NytBU5vJKSjRRdVw59iNNUH5o",[222,227],{"title":223,"path":224,"stem":225,"date":226,"type":219,"children":-1},"MYDB 0. 项目结构和一些不得不说的话","\u002Fprojects\u002Fmydb\u002Fmydb0","posts\u002Fprojects\u002Fmydb\u002Fmydb0","2021-11-27 14:43:00",{"title":228,"path":229,"stem":230,"date":231,"type":219,"children":-1},"MYDB 2. 引用计数缓存框架和共享内存数组","\u002Fprojects\u002Fmydb\u002Fmydb2","posts\u002Fprojects\u002Fmydb\u002Fmydb2","2021-11-30 23:18:00",1787554445995]