简介: 在 MySQL 8.0 之前,Server 层和存储引擎(比如 InnoDB)会各自保留一份元数据(schema name, table definition 等),不仅在信息存储上有着重复冗余,而且可能存在两者之间存储的元数据不同步的现象。不同存储引擎之间(比如 InnoDB 和 MyISAM)有着不同的元数据存储形式和位置(.FRM, .PAR, .OPT, .TRN and .TRG files),造成了元数据无法统一管理。此外,将元数据存放在不支持事务的表和文件中,使得 DDL 变更不会是原子的,crash recovery 也会成为一个问题。

作者 | 泊歌
来源 | 阿里技术公众号

一 背景

在 MySQL 8.0 之前,Server 层和存储引擎(比如 InnoDB)会各自保留一份元数据(schema name, table definition 等),不仅在信息存储上有着重复冗余,而且可能存在两者之间存储的元数据不同步的现象。不同存储引擎之间(比如 InnoDB 和 MyISAM)有着不同的元数据存储形式和位置(.FRM, .PAR, .OPT, .TRN and .TRG files),造成了元数据无法统一管理。此外,将元数据存放在不支持事务的表和文件中,使得 DDL 变更不会是原子的,crash recovery 也会成为一个问题。

为了解决上述问题,MySQL 在 8.0 中引入了 data dictionary 来进行 Server 层和不同引擎间统一的元数据管理,这些元数据都存储在 InnoDB 引擎的表中,自然的支持原子性,且 Server 层和引擎层共享一份元数据,不再存在不同步的问题。

二 整体架构

data dictionary 提供了统一的 client API 供 Server 层和引擎层使用,包含对元数据访问的 acquire() / drop() / store() / update() 基本操作。底层实现了对 InnoDB 引擎存放的数据字典表的读写操作,包含开表(open table)、构造主键、主键查找等过程。client 和底层存储之间通过两级缓存来加速对元数据对象的内存访问,两级缓存都是基于 hash map 实现的,一层缓存是 local 的,由每个 client(每个线程对应一个 client)独享;二级缓存是 share 的,为所有线程共享的全局缓存。下面我将对 data dictionary 的数据结构和实现架构做重点介绍,也会分享一个支持原子的 DDL 在 data dictionary 层面的实现过程。

三 metadata 在内存和引擎层面的表示

data dictionary (简称DD)中的数据结构是完全按照多态、接口/实现的形式来组织的,接口通过纯虚类来实现(比如表示一个表的 Table),其实现类(Table_impl)为接口类的名字加 _impl 后缀。下面以 Table_impl 为例介绍一个表的元数据对象在 DD cache 中的表示。

1 Table_impl

Table_impl 类中包含一个表相关的元数据属性定义,比如下列最基本引擎类型、comment、分区类型、分区表达式等。

class Table_impl : public Abstract_table_impl, virtual public Table {// Fields.Object_id m_se_private_id;String_type m_engine;String_type m_comment;// - Partitioning related fields.enum_partition_type m_partition_type;String_type m_partition_expression;String_type m_partition_expression_utf8;enum_default_partitioning m_default_partitioning;// References to tightly-coupled objects.Index_collection m_indexes;Foreign_key_collection m_foreign_keys;Foreign_key_parent_collection m_foreign_key_parents;Partition_collection m_partitions;Partition_leaf_vector m_leaf_partitions;Trigger_collection m_triggers;Check_constraint_collection m_check_constraints;
};

Table_impl 也是代码实现中 client 最常访问的内存结构,开发者想要增加新的属性,直接在这个类中添加和初始化即可,但是仅仅如此不会自动将该属性持久化到存储引擎中。除了上述简单属性之外,还包括与一个表相关的复杂属性,比如列信息、索引信息、分区信息等,这些复杂属性都是存在其他的 DD 表中,在内存 cache 中也都会集成到 Table_impl 对象里。

从 Abstract_table_impl 继承来的 Collection m_columns 就表示表的所有列集合,集合中的每一个对象 Column_impl 表示该列的元信息,包括数值类型、是否为 NULL、是否自增、默认值等。同时也包含指向 Abstract_table_impl 的指针,将该列与其对应的表联系起来。

class Column_impl : public Entity_object_impl, public Column {// Fields.enum_column_types m_type;bool m_is_nullable;bool m_is_zerofill;bool m_is_unsigned;bool m_is_auto_increment;bool m_is_virtual;bool m_default_value_null;String_type m_default_value;// References to tightly-coupled objects.Abstract_table_impl *m_table;
};

此外 Table_impl 中也包含所有分区的元信息集合 Collection m_partitions,存放每个分区的 id、引擎、选项、范围值、父子分区等。

class Partition_impl : public Entity_object_impl, public Partition {// Fields.Object_id m_parent_partition_id;uint m_number;Object_id m_se_private_id;String_type m_description_utf8;String_type m_engine;String_type m_comment;Properties_impl m_options;Properties_impl m_se_private_data;// References to tightly-coupled objects.Table_impl *m_table;const Partition *m_parent;Partition_values m_values;Partition_indexes m_indexes;Table::Partition_collection m_sub_partitions;
};

因此获取到一个表的 Table_impl,我们就可以获取到与这个表相关联的所有元信息。

2 Table_impl 是如何持久化存储和访问的

DD cache 中的元信息都是在 DD tables 中读取和存储的,每个表存放一类元信息的基本属性字段,比如 tables、columns、indexes等,他们之间通过主外键关联连接起来,组成 Table_impl 的全部元信息。DD tables 存放在 mysql 的表空间中,在 release 版本对用户隐藏,只能通过 INFORMATION SCHEMA 的部分视图查看;在 debug 版本可通过设置 SET debug='+d,skip_dd_table_access_check' 直接访问查看。比如:

root@localhost:test 8.0.18-debug> SHOW CREATE TABLE mysql.tables\G
*************************< strong> 1. row < /strong>*************************Table: tables
Create Table: CREATE TABLE `tables` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`schema_id` bigint(20) unsigned NOT NULL,`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,`type` enum('BASE TABLE','VIEW','SYSTEM VIEW') COLLATE utf8_bin NOT NULL,`engine` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`mysql_version_id` int(10) unsigned NOT NULL,`row_format` enum('Fixed','Dynamic','Compressed','Redundant','Compact','Paged') COLLATE utf8_bin DEFAULT NULL,`collation_id` bigint(20) unsigned DEFAULT NULL,`comment` varchar(2048) COLLATE utf8_bin NOT NULL,`hidden` enum('Visible','System','SE','DDL') COLLATE utf8_bin NOT NULL,`options` mediumtext COLLATE utf8_bin,`se_private_data` mediumtext COLLATE utf8_bin,`se_private_id` bigint(20) unsigned DEFAULT NULL,`tablespace_id` bigint(20) unsigned DEFAULT NULL,`partition_type` enum('HASH','KEY_51','KEY_55','LINEAR_HASH','LINEAR_KEY_51','LINEAR_KEY_55','RANGE','LIST','RANGE_COLUMNS','LIST_COLUMNS','AUTO','AUTO_LINEAR') COLLATE utf8_bin DEFAULT NULL,`partition_expression` varchar(2048) COLLATE utf8_bin DEFAULT NULL,`partition_expression_utf8` varchar(2048) COLLATE utf8_bin DEFAULT NULL,`default_partitioning` enum('NO','YES','NUMBER') COLLATE utf8_bin DEFAULT NULL,`subpartition_type` enum('HASH','KEY_51','KEY_55','LINEAR_HASH','LINEAR_KEY_51','LINEAR_KEY_55') COLLATE utf8_bin DEFAULT NULL,`subpartition_expression` varchar(2048) COLLATE utf8_bin DEFAULT NULL,`subpartition_expression_utf8` varchar(2048) COLLATE utf8_bin DEFAULT NULL,`default_subpartitioning` enum('NO','YES','NUMBER') COLLATE utf8_bin DEFAULT NULL,`created` timestamp NOT NULL,`last_altered` timestamp NOT NULL,`view_definition` longblob,`view_definition_utf8` longtext COLLATE utf8_bin,`view_check_option` enum('NONE','LOCAL','CASCADED') COLLATE utf8_bin DEFAULT NULL,`view_is_updatable` enum('NO','YES') COLLATE utf8_bin DEFAULT NULL,`view_algorithm` enum('UNDEFINED','TEMPTABLE','MERGE') COLLATE utf8_bin DEFAULT NULL,`view_security_type` enum('DEFAULT','INVOKER','DEFINER') COLLATE utf8_bin DEFAULT NULL,`view_definer` varchar(288) COLLATE utf8_bin DEFAULT NULL,`view_client_collation_id` bigint(20) unsigned DEFAULT NULL,`view_connection_collation_id` bigint(20) unsigned DEFAULT NULL,`view_column_names` longtext COLLATE utf8_bin,`last_checked_for_upgrade_version_id` int(10) unsigned NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `schema_id` (`schema_id`,`name`),UNIQUE KEY `engine` (`engine`,`se_private_id`),KEY `engine_2` (`engine`),KEY `collation_id` (`collation_id`),KEY `tablespace_id` (`tablespace_id`),KEY `type` (`type`),KEY `view_client_collation_id` (`view_client_collation_id`),KEY `view_connection_collation_id` (`view_connection_collation_id`),CONSTRAINT `tables_ibfk_1` FOREIGN KEY (`schema_id`) REFERENCES `schemata` (`id`),CONSTRAINT `tables_ibfk_2` FOREIGN KEY (`collation_id`) REFERENCES `collations` (`id`),CONSTRAINT `tables_ibfk_3` FOREIGN KEY (`tablespace_id`) REFERENCES `tablespaces` (`id`),CONSTRAINT `tables_ibfk_4` FOREIGN KEY (`view_client_collation_id`) REFERENCES `collations` (`id`),CONSTRAINT `tables_ibfk_5` FOREIGN KEY (`view_connection_collation_id`) REFERENCES `collations` (`id`)
) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB AUTO_INCREMENT=549 DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC
1 row in set (0.00 sec)

通过以上 mysql.tables 的表定义可以获得存储引擎中实际存储的元信息字段。DD tables 包括 tables、schemata、columns、column_type_elements、indexes、index_column_usage、foreign_keys、foreign_key_column_usage、table_partitions、table_partition_values、index_partitions、triggers、check_constraints、view_table_usage、view_routine_usage 等。

Storage_adapter 是访问持久存储引擎的处理类,包括 get() / drop() / store() 等接口。当初次获取一个表的元信息时,会调用 Storage_adapter::get() 接口,处理过程如下:

Storage_adapter::get()// 根据访问对象类型,将依赖的 DD tables 加入到 open table list 中|--Open_dictionary_tables_ctx::register_tables< T>() |--Table_impl::register_tables()|--Open_dictionary_tables_ctx::open_tables() // 调用 Server 层接口打开所有表|--Raw_table::find_record() // 直接调用 handler 接口根据传入的 key(比如表名)查找记录|--handler::ha_index_read_idx_map() // index read// 从读取到的 record 中解析出对应属性,调用 field[field_no]->val_xx() 函数|--Table_impl::restore_attributes()// 通过调用 restore_children() 函数从与该对象关联的其他 DD 表中根据主外键读取完整的元数据定义|--Table_impl::restore_children() |--返回完整的 DD cache 对象

上述在获取列和属性的对应关系时,根据的是 Tables 对象的枚举类型下标,按顺序包含了该类型 DD 表中的所有列,与上述表定义是一一对应的。因此如果我们需要新增 DD 表中存储的列时,也需要往下面枚举类型定义中加入对应的列,并且在 Table_impl::restore_attributes() / Table_impl::store_attributes() 函数中添加对新增列的读取和存储操作。

class Tables : public Entity_object_table_impl {enum enum_fields {FIELD_ID,FIELD_SCHEMA_ID,FIELD_NAME,FIELD_TYPE,FIELD_ENGINE,FIELD_MYSQL_VERSION_ID,FIELD_ROW_FORMAT,FIELD_COLLATION_ID,FIELD_COMMENT,FIELD_HIDDEN,FIELD_OPTIONS,FIELD_SE_PRIVATE_DATA,FIELD_SE_PRIVATE_ID,FIELD_TABLESPACE_ID,FIELD_PARTITION_TYPE,FIELD_PARTITION_EXPRESSION,FIELD_PARTITION_EXPRESSION_UTF8,FIELD_DEFAULT_PARTITIONING,FIELD_SUBPARTITION_TYPE,FIELD_SUBPARTITION_EXPRESSION,FIELD_SUBPARTITION_EXPRESSION_UTF8,FIELD_DEFAULT_SUBPARTITIONING,FIELD_CREATED,FIELD_LAST_ALTERED,FIELD_VIEW_DEFINITION,FIELD_VIEW_DEFINITION_UTF8,FIELD_VIEW_CHECK_OPTION,FIELD_VIEW_IS_UPDATABLE,FIELD_VIEW_ALGORITHM,FIELD_VIEW_SECURITY_TYPE,FIELD_VIEW_DEFINER,FIELD_VIEW_CLIENT_COLLATION_ID,FIELD_VIEW_CONNECTION_COLLATION_ID,FIELD_VIEW_COLUMN_NAMES,FIELD_LAST_CHECKED_FOR_UPGRADE_VERSION_ID,NUMBER_OF_FIELDS  // Always keep this entry at the end of the enum};
};

四 多级缓存

为了避免每次对元数据对象的访问都需要去持久存储中读取多个表的数据,使生成的元数据内存对象能够复用,data dictionary 实现了两级缓存的架构,第一级是 client local 独享的,核心数据结构为 Local_multi_map,用于加速在当前线程中对于相同对象的重复访问,同时在当前线程涉及对 DD 对象的修改(DDL)时管理 committed、uncommitted、dropped 几种状态的对象。第二级就是比较常见的多线程共享的缓存,核心数据结构为 Shared_multi_map,包含着所有线程都可以访问到其中的对象,所以会做并发控制的处理。

两级缓存的底层实现很统一,都是基于 hash map 的,目前的实现是 std::map。Local_multi_map 和 Shared_multi_map都是派生于 Multi_map_base。

之所以叫 Multi_map_base,是因为其中包含了多个 hash map,适合用户根据不同类型的 key 来获取缓存对象,比如 id、name、DD cache 本身等。Element_map 就是对 std::map 的一个封装,key 为前述几种类型之一,value 为 DD cache 对象指针的一个封装 Cache_element,封装了对象本身和引用计数。

Multi_map_base 对象实现了丰富的 m_map() 模板函数,可以很方便的根据 key 的类型不同选择到对应的 hash map。

Shared_multi_map 与 Local_multi_map 的不同在于,Shared_multi_map 还引入了一组 latch 与 condition variable 用于并发访问中的线程同步与 cache miss 的处理。同时对 Cache_element 对象做了内存管理和复用的相关能力。

1 局部缓存

一级缓存位于每个 Dictionary_client (每个 client 与线程 THD 一一对应)内部,由不同状态(committed、uncommitted、dropped)的 Object_registry 组成。每个 Object_registry 由不同元数据类型的 Local_multi_map 组成,用于管理不同类型的对象(比如表、schema、字符集、统计数据、Event 等)缓存。

其中 committed 状态的 registry 就是我们访问数据库中已经存在的对象时,将其 DD cache object 存放在局部缓存中的位置。uncommitted 和 dropped 状态的存在,主要用于当前连接执行的是一条 DDL 语句,在执行过程中会将要 drop 的旧表对应的 DD object 存放在 dropped 的 registry 中,将还未提交的新表定义对应的 DD object 存放在 uncommitted 的 registry 中,用于执行状态的区分。

2 共享缓存

共享缓存是 Server 全局唯一的,使用单例 Shared_dictionary_cache 来实现。与上述局部缓存中 Object_registry 相似,Shared_dictionary_cache 也需要包含针对各种类型对象的缓存。与 Multi_map_base 实现根据 key 类型自动选取对应 hash map 的模版函数相似,Object_registry 和 Shared_dictionary_cache 也都实现了根据访问对象的类型选择对应缓存的 m_map() 函数,能够很大程度上简化函数调用。

与局部缓存可以无锁访问 hash map 不同,共享缓存在获取 / 释放 DD cache object 时都需要加锁来完成引用计数的调整和防止访问过程中被 destroy 掉。

3 缓存获取过程

用户通过 client 调用元数据对象获取函数,传入元数据的 name 字符串,然后构建出对应的 name key,通过 key 去缓存中获取元数据对象。获取的整体过程就是一级局部缓存 -> 二级共享缓存 -> 存储引擎。

// Get a dictionary object.
template < typename K, typename T>
bool Dictionary_client::acquire(const K &key, const T **object,bool *local_committed,bool *local_uncommitted) {// Lookup in registry of uncommitted objectsT *uncommitted_object = nullptr;bool dropped = false;acquire_uncommitted(key, &uncommitted_object, &dropped);...// Lookup in the registry of committed objects.Cache_element< T> *element = NULL;m_registry_committed.get(key, &element);...// Get the object from the shared cache.if (Shared_dictionary_cache::instance()->get(m_thd, key, &element)) {DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||m_thd->is_error());return true;}
}

在一级局部缓存中获取时,会优先去 uncommitted 和 dropped 的 registry 获取,因为这两者是最新的修改,同时判断获取对象是否已经被 dropped。之后再会去 committed 的 registry 获取,如果获取到就直接返回,反之则去二级共享缓存中尝试获取。

Cache miss

共享缓存的获取过程在 Shared_multi_map::get() 中实现。就是加锁后直接的 hash map 查找,如果存在则给引用计数递增后返回;如果不存在,就会进入到 cache miss 的处理过程,调用上面介绍的存储引擎的接口 Storage_adapter::get() 从 DD tables 中读取,创建出来后依次加入共享缓存和局部缓存 committed registry 中。

由于开表访问 DD tables,构建 DD cache object 的过程相对耗时,不会一直给 Shared_multi_map 加锁,因此需要对并发访问的 client 做并发控制。DD 的实现方法是第一个访问的 client 会将 cache miss 的 key 加入到 Shared_multi_map的 m_missed 集合中,这个集合包含着现在所有正在读取元数据的对象 key 值。之后访问的 client 看到目标 key 值在 m_missed 集合中就会进入等待。

当第一个 client 获取到完整的 DD cache object,加入到共享缓存之后,移除 m_missed 集合中对应的 key,并通过广播的方式通知之前等待的线程重新在共享缓存中获取。

五 Auto_releaser

Auto_releaser 是一个 RAII 类,基本上在使用 client 访问 DD cache 前都会做一个封装,保证在整个 Auto_releaser 对象存在的作用域内,所获取到的 DD cache 对象都会在局部缓存中存在不释放。Auto_releaser 包含需要 release 的对象 registry,通过 auto_release() 函数收集着当前 client 从共享缓存中获取到的 DD cache 对象,在超出其作用域进行析构时自动 release 对象,从局部缓存 committed 的 registry 中移除对象,并且在共享缓存中的引用计数递减。

在嵌套函数调用过程中,可能在每一层都会有自己的 Auto_releaser,他们之间通过一个简单的链表指针连接起来。在函数返回时将本层需要 release 的对象 release 掉,需要返回给上层使用的 DD cache 对象交给上层的 Auto_releaser 来负责。通过 transfer_release() 可以在不同层次的 Auto_releaser 对象间转移需要 release 的对象,可以灵活的指定不再需要 DD cache 对象的层次。

六 应用举例:inplace DDL 过程中对 DD 的操作

在 MySQL inplace DDL 执行过程中,会获取当前表定义的 DD cache 对象,然后根据实际的 DDL 操作内容构造出新对应的 DD 对象。然后依次调用 client 的接口完成对当前表定义的删除和新表定义的存储。

{    if (thd->dd_client()->drop(table_def)) goto cleanup2;table_def = nullptr;DEBUG_SYNC_C("alter_table_after_dd_client_drop");// Reset check constraint's mode.reset_check_constraints_alter_mode(altered_table_def);if ((db_type->flags & HTON_SUPPORTS_ATOMIC_DDL)) {/*For engines supporting atomic DDL we have delayed storing newtable definition in the data-dictionary so far in order to avoidconflicts between old and new definitions on foreign key names.Since the old table definition is gone we can safely store newdefinition now.*/if (thd->dd_client()->store(altered_table_def)) goto cleanup2;}
}.../*If the SE failed to commit the transaction, we must rollback themodified dictionary objects to make sure the DD cache, the DDtables and the state in the SE stay in sync.
*/
if (res)thd->dd_client()->rollback_modified_objects();
elsethd->dd_client()->commit_modified_objects();

在 drop() 过程中,会将当前表定义的 DD cache 对象对应的数据从存储引擎中删除,然后从共享缓存中移除(这要求当前对象的引用计数仅为1,即只有当前线程使用),之后加入到 dropped 局部缓存中。

在 store() 过程中,会将新的表定义写入存储引擎,并且将对应的 DD cache 对象加入 uncommitted 缓存中。

在事务提交或者回滚后,client 将局部缓存中的 dropped 和 uncommitted registry 清除。由于 InnoDB 引擎支持事务,持久存储层面的数据会通过存储引擎的接口提交或回滚,不需要 client 额外操作。

在这个过程中,由于 MDL(metadata lock) 的存在,不会有其他的线程尝试访问正在变更对象的 DD object,所以可以安全的对 Shared_dictionary_cache 进行操作。当 DDL 操作结束(提交或回滚),释放 EXCLUSIVE 锁之后,新的线程就可以重新从存储引擎上加载新的表定义。

七 总结

MySQL data dictionary 解决了背景所述旧架构中的诸多问题,使元数据的访问更加安全,存储和管理成本更低。架构实现非常的精巧,通过大量的模版类实现使得代码能够最大程度上被复用。多层缓存的实现也能显著提升访问效率。通过 client 简洁的接口,让 Server 层和存储层能在任何地方方便的访问元数据。

原文链接

本文为阿里云原创内容,未经允许不得转载。

MySQL 深潜 - 一文详解 MySQL Data Dictionary相关推荐

  1. mysql mgr 三节点_详解MySQL 5.7 MGR单主确定主节点方法

    我们行MGR年底要上线了,每天都要看官方文档学习,做测试,坚持每天写一个小知识点,有想一起学习的么~ MySQL 5.7 MGR单主确定主节点是哪个,我们可以通过成员ID来判断,然后结合read_on ...

  2. mysql 编辑数据库内容_详解mysql数据库增删改操作

    插入数据 insert into 表名(列名1,列名2,列名3) values(值1,值2,值3); insert into user(user_id,name,age) values(1,'nice ...

  3. mysql临键锁_详解 MySql InnoDB 中的三种行锁(记录锁、间隙锁与临键锁)

    详解 MySql InnoDB 中的三种行锁(记录锁.间隙锁与临键锁) 前言 InnoDB 通过 MVCC 和 NEXT-KEY Locks,解决了在可重复读的事务隔离级别下出现幻读的问题.MVCC  ...

  4. mysql 的dcl语句_详解MySQL第三篇—DCL语句

    DCL(Data Control Language)语句:数据控制语句,用于控制不同数据段直接的许可和访问级别的语句.这些语句定义了数据库.表.字段.用户的访问权限和安全级别.主要的语句关键字包括 g ...

  5. mysql通配符escape使用_详解MySQL like如何查询包含'%'的字段(ESCAPE用法)

    在SQl like语句中,比如 SELECT * FROM user WHERE username LIKE '%luchi%' SELECT * FROM user WHERE username L ...

  6. mysql通配符escape使用_详解MySQL like如何查询包含#39;%#39;的字段(ESCAPE用法)-MySQL教程-Web开发者网...

    在SQl like语句中,比如 SELECT * FROM user WHERE username LIKE '%luchi%' SELECT * FROM user WHERE username L ...

  7. 一文详解 MySQL 高可用之 DRBD | 原力计划

    作者 | wzy0623 责编 | 屠敏 出品 | CSDN 博客 大多数MySQL高可用解决方案都是基于MySQL自带的各种复制技术.本质上是将一个实例上的数据更新或事务,在其它实例上进行重放,从而 ...

  8. 没有执行此操作所需的足够可用空间。_一文详解 MySQL 高可用之 DRBD | 原力计划...

    作者 | wzy0623责编 | 屠敏出品 | CSDN 博客大多数MySQL高可用解决方案都是基于MySQL自带的各种复制技术.本质上是将一个实例上的数据更新或事务,在其它实例上进行重放,从而完成数 ...

  9. 一文详解MySQL中的事件调度器EVENT

    MySQL中的事件调度器,EVENT,也叫定时任务,类似于Unix crontab或Windows任务调度程序. EVENT由其名称和所在的schema唯一标识. EVENT根据计划执行特定操作.操作 ...

最新文章

  1. 诺顿360“偷偷”挖矿被怒喷,杀毒软件手伸向GPU,官方:都是为了用户好
  2. 汇编语言随笔(8)-实验9(显示字符串)、实验10(子程序:除法溢出,数值到字符串的转换)和课程设计1
  3. ABAP实例:如何设计和使用自定义的权限对象
  4. IO多路复用之select篇
  5. 服务器上出现应用程序错误。此应用程序的当前自定义错误设置禁止远程查看应用程序_手把手教你使用Qlik(一):数据关联并创建应用程序...
  6. Spring Boot 2.3 版本变化[翻译]
  7. 利用回溯法解决1-9之间添加+或-或使得运算结果为100的问题
  8. Android之Surface/Window/View/SurfaceView区别
  9. 初识Jasima-调度仿真系列教程预告
  10. noip2002 矩形覆盖
  11. CATIA软件常见问题解答
  12. 经纬度转WGS84坐标
  13. 突然间思考PID 有意思的地方-为什么说开环控制最优这句话也对也不对
  14. 地平线 J2J3了解笔记
  15. 微博登录界面的PHP代码,redis+php实现微博(一)注册与登录功能详解
  16. 2020年小红书直播报告
  17. 科创、创业板块崛起,天弘科创创业50指数基金缘何备受热捧?
  18. (一)1.线性规划 之 化标准型和图解法
  19. sql查询结果加一行合计
  20. 【第3次实验】卷积神经网络

热门文章

  1. JAVA入门级教学之(算数运算符)
  2. Linux7添加syslog,请教linux下安装syslog的方法.是安装,不是配置
  3. c++ map底层_深入浅出Redisredis底层数据结构(上)
  4. js foreach 跳出循环_VUE.js
  5. import oracle utility_教你如何Oracle数据导入
  6. tracepro杂散光分析例子_光刻机的蜕变过程及专利分析
  7. 空调调节 java_空调调节方式
  8. matlab ode45求解齿轮动力学,ode45求解多自由度动力学方程实例.doc
  9. pakeage php国内镜像,Packagist/Composer中国全量镜像 | 严佳冬
  10. java支持多线程吗_Java多线程之一