NDB
支持在线架构更改。诸如 aTable
或
之类的模式对象Index
具有 4 字节
模式对象版本标识符,可以在
ndb_desc实用程序的输出中观察到(请参阅
ndb_desc — 描述 NDB 表),如下所示(强调文本):
$> ndb_desc -c 127.0.0.1 -d test t1
-- t1 --
Version: 33554434
Fragment type: HashMapPartition
K Value: 6
Min load factor: 78
Max load factor: 80
Temporary table: no
Number of attributes: 3
Number of primary keys: 1
Length of frm data: 269
Row Checksum: 1
Row GCI: 1
SingleUserMode: 0
ForceVarPart: 1
FragmentCount: 4
ExtraRowGciBits: 0
ExtraRowAuthorBits: 0
TableStatus: Retrieved
HashMap: DEFAULT-HASHMAP-240-4
-- Attributes --
c1 Int PRIMARY KEY DISTRIBUTION KEY AT=FIXED ST=MEMORY AUTO_INCR
c2 Int NULL AT=FIXED ST=MEMORY
c4 Varchar(50;latin1_swedish_ci) NOT NULL AT=SHORT_VAR ST=MEMORY
-- Indexes --
PRIMARY KEY(c1) - UniqueHashIndex
PRIMARY(c1) - OrderedIndex
NDBT_ProgramExit: 0 - OK
模式对象版本标识符(或简称为“模式版本”)由主要版本和次要版本组成;主要版本占据模式版本的(单个)最低有效字节,次要版本占据剩余的(3 个最高有效)字节。在以十六进制表示法查看架构版本时,您可以更轻松地看到这两个组件。在刚刚显示的示例输出中,模式版本显示为
33554434
,十六进制(根据需要填充前导零)为0x02000002
;这相当于主要版本 2,次要版本 2。向表添加索引会导致ndb_desct1
报告的模式版本前进到
50331650
,或0x03000002
十六进制,相当于主要版本 2(3 个最低有效字节00 00 02
),次要版本 3(最高有效字节03
)。对于新创建的表,次要模式版本以 0 开头。
此外,每个 NDB API 数据库对象类都有自己的
getObjectVersion()
方法,例如
Object::getObjectVersion()
返回对象的模式对象版本。这包括实例,不仅包括 ,还包括Object
、Table
、
Index
、
Column
、
LogfileGroup
、
Tablespace
和
Datafile
,
Undofile
以及
Event
。(但是,
NdbBlob::getVersion()
其目的和功能与刚刚列出的方法完全无关。)
被认为是向后兼容的架构更改——例如在表的末尾添加一个DEFAULT
或NULL
列——会导致表对象的次要版本增加。不被视为向后兼容的架构更改(例如从表中删除列)会导致主要版本增加。
虽然导致模式主要版本更改的操作的实现实际上可能涉及受影响表的 2 个副本(删除并重新创建表),但最终结果可以观察到表的主要版本增加。
来自 NDB 客户端的查询和 DML 操作也有一个关联的模式版本,它在数据节点处理开始时被检查。如果请求的模式版本与受影响的数据库对象的最新模式版本仅在其次要版本组件上不同,则该操作被认为是兼容的并允许继续进行。如果模式版本与主要模式版本不同,那么它将被拒绝。
这种机制允许以各种方式在数据节点中更改模式,而不需要在客户端中同步模式更改。在客户准备好之前,他们不需要继续使用新的模式版本。查询和 DML 操作因此可以不间断地继续进行。
NDB API 和模式对象版本。
NDB API 应用程序通常使用与
NdbDictionary
对象关联的Ndb
对象来检索模式对象。根据需要从数据节点检索模式对象;信令用于获取表或索引定义;然后,构建应用程序可以使用的本地内存对象。NDB
在内部缓存模式对象,因此对同一个表或按名称的索引的每个连续请求都不需要信号。
全局模式缓存。
为了避免在每次模式对象查找时都需要向数据节点发送信号,每个
Ndb_cluster_connection
. 这称为全局模式缓存。它在跨越多个 Ndb 对象方面是全局的。实例化的表和索引对象会自动放入此缓存中,以节省未来的信号和实例化成本。缓存为每个对象维护一个引用计数;此计数用于确定何时可以删除给定的模式对象。模式对象可以通过显式 API 方法调用或本地模式缓存操作来修改它们的引用计数。
本地模式缓存。
除了每个连接的全局模式缓存之外,每个
Ndb
对象的
NdbDictionary
对象都有一个
本地模式缓存。此缓存包含指向全局模式缓存中保存的对象的指针。每个保存对全局模式缓存中模式对象的引用的本地模式缓存都会将全局模式缓存引用计数递增 1。拥有每个Ndb
对象本地的模式缓存允许在不强加任何锁的情况下查找模式对象。Ndb
当其关联对象被删除
时,本地模式缓存通常会被清空(减少进程中的全局缓存引用计数) 。
没有架构更改的操作。 在下列情况下,正常操作如下:
-
某个客户端(Ndb 对象)第一次请求一个表。 检查本地缓存;尝试导致未命中。然后还检查全局缓存(使用锁),结果是另一个未命中。
由于没有缓存命中,向数据节点发送信号;节点的响应用于实例化表对象。指向实例化数据对象的指针被添加到全局缓存中;将另一个这样的指针添加到本地缓存,并将引用计数设置为 1。指向表的指针返回给客户端。
-
第二个客户端(不同的 Ndb 对象)请求访问同一个表,也是通过名称。 检查本地缓存会导致未命中,但检查全局缓存会产生命中。
结果,一个对象指针被添加到本地缓存,全局引用计数增加——所以它的值现在是 2——一个对象指针被返回给客户端。没有新的指针被添加到全局缓存中。
第二次,第二个客户端请求按名称访问同一个表。 检查本地缓存,产生命中。对象指针立即返回给客户端。没有指针被添加到本地或全局缓存,并且对象的引用计数没有增加(因此引用计数保持不变为 2)。
-
第二个客户端删除 Ndb 对象。 此客户端的本地模式缓存中的对象在全局缓存中减少了它们的引用计数。
这会将全局缓存引用计数设置为 1。由于它还不是 0,因此尚未采取任何操作来删除父
Ndb
对象。
架构更改。
假设一个对象的模式永远不会改变,首先检索到的模式版本在应用程序进程的生命周期内使用,只有当所有本地缓存引用(即所有对
Ndb
对象的引用)都被删除时,内存中的对象才会被删除。除了在关闭或集群连接重置期间,这种情况不太可能发生。
如果在应用程序运行时对象的架构以向后兼容的方式更改,则会产生以下影响:
数据节点上的次要版本递增。(使用旧模式版本的持续 DML 操作仍然成功。)
NDB API 客户端随后检索最新版本的模式对象,然后获取新的模式版本。
具有缓存旧版本的 NDB API 客户端不使用新模式版本,除非并且直到它们的本地和全局缓存失效。
订阅事件的 NDB API 客户端可以观察到
TE_ALTER
相关表的事件,并可以使用它来触发模式对象缓存失效。可以通过调用
removeCachedTable()
或 来删除每个本地缓存条目removeCachedIndex()
。这将从本地缓存中删除条目,并减少全局缓存中的引用计数。当(并且如果)全局缓存引用计数达到零时,可以删除旧的缓存对象。或者,可以通过调用
invalidateTable()
or 删除本地缓存条目,并使全局缓存条目无效invalidateIndex()
。对该客户端和其他客户端的后续调用通过向数据节点发送信号并实例化新对象来返回新的模式对象getTable()
版本 。getIndex()
新
Ndb
对象照常从全局表缓存中按需填充其本地表缓存。这意味着,一旦旧模式对象在全局缓存中失效,此类对象将检索表对象首次缓存时已知的最新表对象。
当进行不兼容的模式更改(即模式主要版本更改)时,一旦提交新版本,使用旧版本的 NDB API 请求就会失败。这也可以用作检索新模式对象版本的触发器。
管理架构版本更改处理的规则总结在以下列表中:
在线模式更改(次要版本更改)不会影响现有客户端(
Ndb
对象);客户端可以继续使用旧的模式对象版本当且仅当客户端通过 API 调用自愿删除缓存的对象时,它才能观察到新的模式对象版本。
当
Ndb
对象移除缓存对象并被删除时,旧模式对象版本的引用计数会减少。当此引用计数达到 0 时,可以删除该对象。
模式对象生命周期的含义。
模式对象(例如 a
Table
或
Index
)的生命周期受Ndb
从中获取它的对象的生命周期限制。当Ndb
模式对象的父对象被删除时,使Ndb
对象保持活动状态的引用计数会减少。如果此Ndb
对象持有对给定模式对象版本的最后引用,则
Ndb
对象的删除也可能导致模式对象的删除。因此,此时没有其他线程可以使用该对象。
Ndb
当指向模式对象的指针保存在应用程序中并在多个对象
之间使用时,必须小心
。模式对象的使用不应超出
Ndb
创建它的对象的生命周期。
应用程序可以异步且彼此独立地响应向后兼容的架构更改,仅在必要时才移动到新架构。不同的线程可以同时操作不同的模式对象版本。
因此,确保模式对象不会比Ndb
用于创建它们的对象寿命更长是非常重要的。为了帮助防止这种情况发生,您可以采取以下任何操作来使旧架构对象失效:
要在需要时触发失效,请使用 NDB API
TE_ALTER
事件(请参阅 Event::TableEvent)。使用外部触发器启动失效。
显式执行定期失效。
以这些方式中的任何一种使缓存失效允许应用程序根据需要获得新版本的模式对象。
还值得注意的是,并非所有 NDB API
Table
getter 方法都返回指针;其中许多(除了
Table::getName()
)返回表名。这些方法包括
Index::getTable()
、
NdbOperation::getTableName()
、
Event::getTableName()
和
NdbDictionary::getRecordTableName()
。