您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
由 Mybatis 源码畅谈软件设计(二):MappedStatement 和 SqlSource
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
由 Mybatis 源码畅谈软件设计(二):MappedStatement 和 SqlSource
wy****
2024-12-22
IP归属:北京
37浏览
本节我们来介绍 `org.apache.ibatis.mapping.MappedStatement`(映射SQL语句声明的类),它是 MyBatis 框架中的一个核心类,也是向后不断学习 Mybatis 源码的基础。在这部分源码中,最值得关注的设计原则是“**信息隐藏**”,它是在《软件设计哲学》中提到的一个观点,简单来说就是将把外部类不需要了解的信息隐藏在类内部,遵循最小知识原则,在与 `MappedStatement` 相关的类中,定义了很多内部类和内部接口,它们只在某些类内访问。此外,`MappedStatement` 的创建完美地遵循了 **建造者模式**,这也是学习该模式很好的实例。这部分源码较多,在看这部分源码时,也需要留意一下方法的长度,尝试着理解在《代码整洁之道》中强调的“**每个方法只做一件事**”的原则,考虑书中提到的按照方法行数拆分多个小方法的原则到底合适不合适,并关注方法的编排顺序,它是胡乱的安排方法的位置还是有一定规律?接下来,我们步入正文。 `MappedStatement` 负责存储和管理映射 SQL 语句的详细信息,每个 `MappedStatement` 对象对应 XML 映射文件中一个 `<select>`, `<insert>`, `<update>`, 或 `<delete>` 标签,其中重要字段内容信息如下: ```java public final class MappedStatement { // 每个 MappedStatement 对象都有一个唯一的 ID,用于在 MyBatis 配置中标识和引用该语句(Mapper接口中的方法的全路径名称) // eg: org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor private String id; // sqlSource 存储 SQL 语句,区分静态、动态SQL private SqlSource sqlSource; // 描述输入参数的类型和映射关系 private ParameterMap parameterMap; // 描述返回结果的类型和映射关系 private List<ResultMap> resultMaps; // SQL 类型 private SqlCommandType sqlCommandType; // 数据库厂商标识,用于多数据库支持 private String databaseId; // Mapper.xml 文件的路径,eg: org/apache/ibatis/builder/AuthorMapper.xml private String resource; // ... } ``` `org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode` 方法是将 xml 文件中配置的 SQL 实例化成 `MappedStatement` 对象的方法: ```java public class XMLStatementBuilder extends BaseBuilder { // ... private final MapperBuilderAssistant builderAssistant; public void parseStatementNode() { // 处理配置的参数 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); // ... // 创建 SqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); // ... // 创建 MappedStatement 对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect); } } ``` 验证创建 `MappedStatement` 对象过程逻辑采用的单测为 `org.apache.ibatis.session.SqlSessionTest.shouldExecuteSelectOneAuthorUsingMapperClass`,解析的 xml 文件为 `AuthorMapper.xml`。 在上述源码步骤中 `SqlSource` 的创建非常重要,该接口的源码注释如下: > Represents the content of a mapped statement read from an XML file or an annotation. It creates the SQL that will be passed to the database out of the input parameter received from the user. 表示从 XML 文件或注释读取的 SQL 语句,它可以根据用户输入的参数创建之后传递给数据库的SQL(`org.apache.ibatis.mapping.SqlSource#getBoundSql` 方法)。接下来我们重点看下这个接口: ```java public interface SqlSource { // 接受参数对象,并返回 BoundSql 对象。BoundSql 包含了生成的 SQL 语句以及相应的参数信息 BoundSql getBoundSql(Object parameterObject); } ``` MyBatis 提供了几个 `SqlSource` 的实现类,每个实现类适用于不同的场景: 1. **`StaticSqlSource`**: 用于处理静态 SQL 语句 2. **`DynamicSqlSource`**: 用于处理动态 SQL 语句(包含 `${}` 占位符和 `<if>`, `<choose>`, `<when>`, `<otherwise>`, `<foreach>` 等动态标签) 3. **`RawSqlSource`**: 这个实现类用于解析包含 `#{}` 占位符的 SQL 语句,其内部实现依然是 `StaticSqlSource` 4. **`ProviderSqlSource`**: 用于处理使用 `@Provider` 注解的 SQL 它的实例化借助了 `XMLScriptBuilder` 实现: ```java public class XMLLanguageDriver implements LanguageDriver { // ... @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); } } ``` `XMLScriptBuilder` 中定义了 **内部接口** 动态标签解析器 `NodeHandler`,它对多种标签解析做了实现,负责将不同标签内容解析成不同的 `SqlNode` 对象: ```java public class XMLScriptBuilder extends BaseBuilder { private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>(); public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化SQL节点处理器 initNodeHandlerMap(); } private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); } // 定义内部接口,因为其只与 XMLScriptBuilder 相关,隐藏在内部不泄露知识,降低复杂度 private interface NodeHandler { void handleNode(XNode nodeToHandle, List<SqlNode> targetContents); } private class WhereHandler implements NodeHandler { // ... } // ... } ``` `org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.NodeHandler#handleNode` 方法的第 2 个入参为 SQL 的节点判断信息,我们需要了解下 `SqlNode` 接口: ```java public interface SqlNode { /** * 负责将当前节点的 SQL 片段应用到上下文中 */ boolean apply(DynamicContext context); } ``` 它的实现类如下所示: ![SqlNode.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-12-15-14-300w7MjDq0KQ7ZjDP.png) * `StaticTextSqlNode` 用于处理不包含占位符的纯文本节点 * `TextSqlNode` 用于处理包含 `${}` 占位符的文本节点,它会 **直接将占位符中的内容拼接到 SQL 上**,所以它能实现 **动态 SQL**,与处理 `#{}` 占位符不同,`#{}` 占位符在处理字符串类型时,会添加上 `'` 单引号,避免 SQL 注入问题,注意它们的区别,后续针对动态 SQL 问题还会讲解 * `MixedSqlNode` 比较特殊,因为 SQL 被解析完毕后,会有多个节点片段,这个对象是将所有的节点(`SqlNode`)保存起来,通过遍历来触发将 SQL 应用到上下文的 `apply` 方法: ```java public class MixedSqlNode implements SqlNode { // 保存 SQL 中所有的节点片段 private final List<SqlNode> contents; public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } @Override public boolean apply(DynamicContext context) { contents.forEach(node -> node.apply(context)); return true; } } ``` 其他实现类根据其命名表示 SQL 中对应的不同标签节点,非常简单就不再赘述了。我们继续回到 `XMLScriptBuilder#`parseScriptNode\` 方法,它会对 xml 文件中定义的 SQL 进行解析: ```java public class XMLScriptBuilder extends BaseBuilder { // ... public SqlSource parseScriptNode() { // 将 xml 文件中定义的 SQL 解析成不同的 SqlNode 对象,并都记录在 MixedSqlNode 对象中 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { // 包含动态标签的 SQL sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { // 静态 SQL 会将占位符 #{} 替换成 ? sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; } // ... } ``` 可以发现它会根据静态或动态 SQL 创建不同的 `SqlSource` 对象,那么我们接下来分别看一下两种不同 SQL 的解析过程。 ### 静态 SQL 的解析 以如下 SQL 为例: ```xml <select id="selectAuthor" parameterMap="selectAuthor" resultMap="selectAuthor"> select id, username, password, email, bio, favourite_section from author where id = #{id,jdbcType=INTEGER, javaType=int} </select> ``` `org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.parseDynamicTags` 方法会执行具体的 SQL 解析逻辑,它会执行如下逻辑:如果包含 `${}` 占位符,会被标识为动态 SQL,对应的类型为 `TextSqlNode`,否则为 `StaticTextSqlNode` 对象,如下逻辑: ```java public class XMLScriptBuilder extends BaseBuilder { public SqlSource parseScriptNode() { // 将 xml 文件中定义的 SQL 解析成不同的 SqlNode 对象,并都记录在 MixedSqlNode 对象中 MixedSqlNode rootSqlNode = parseDynamicTags(context); // ... } protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); // 包含 ${} 占位符的会被判断为动态 SQL,并被记录为 TextSqlNode if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // ... } } return new MixedSqlNode(contents); } } ``` 其中 `textSqlNode.isDynamic()` 步骤需要关注。`TextSqlNode` 对象中定义了 `DynamicCheckerTokenParser` **内部类**,它实现了 `TokenHandler` 接口,在路径 `org.apache.ibatis.parsing` 下,根据包路径描述 “Parsing utils”,可知该接口是解析工具类通用接口,不过 `DynamicCheckerTokenParser` 并没有做什么需要特别关注的逻辑,只是用于标记 SQL 为静态 SQL 还是动态 SQL(`isDynamic` 字段): ```java private static class DynamicCheckerTokenParser implements TokenHandler { private boolean isDynamic; public DynamicCheckerTokenParser() { // Prevent Synthetic Access } public boolean isDynamic() { return isDynamic; } @Override public String handleToken(String content) { this.isDynamic = true; return null; } } ``` 在 `textSqlNode.isDynamic()` 方法中,创建了 `DynamicCheckerTokenParser` 对象和 `GenericTokenParser` 对象。 ```java public class TextSqlNode implements SqlNode { // ... public boolean isDynamic() { DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser(); GenericTokenParser parser = createParser(checker); parser.parse(text); return checker.isDynamic(); } // ... } ``` `GenericTokenParser` 译为通用的 Token 解析器,同样也在 `org.apache.ibatis.parsing` 包路径下,它会完成判断是否为动态 SQL 的逻辑(仅判断是否包含 `${}` 占位符),之后会经常看到它的身影,其中包含三个字段: ```java public class GenericTokenParser { // 要匹配的 占位符左边界信息 private final String openToken; // 要匹配的 占位符右边界信息 private final String closeToken; // 组合 TokenHandler 来实现“替换占位符”的功能,符合单一职责的原则 private final TokenHandler handler; // ... // 解析 SQL 操作 public String parse(String text) { // ... } } ``` 继续回到 `org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.parseDynamicTags` 方法,如果在 SQL 中匹配到 `${}` 则 `DynamicCheckerTokenParser` 会将 `isDynamic` 标记为 `true`,当前 SQL 节点会被定义为 `TextSqlNode` 类型,否则为 `StaticTextSqlNode` 类型,因为在示例中定义的 SQL 是带有 `#{}` 占位符的 SQL,所以 `SqlSource` 会被解析成 `RawSqlSource` 类型,并在其中组合 `StaticSqlSource` 对象。 ```java public class RawSqlSource implements SqlSource { private final SqlSource sqlSource; public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) { SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> clazz = parameterType == null ? Object.class : parameterType; // 静态 SQL 在此处被解析,包含 #{} 占位符的会被替换为 ? sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); } // ... } ``` 在 `RawSqlSource` 的构造方法中,完成了替换占位符的操作,`SqlSourceBuilder` 对象需要重点关注,由其命名来看,它是 `SqlSource` 对象的“建造者(Builder)”,但是它并没有严格遵守建造者模式,不过是将复杂的构造对象的逻辑隐藏起来(**信息隐藏**): ```java public class SqlSourceBuilder extends BaseBuilder { public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { // 用于将占位符替换为 ? 的 handler ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql; // 配置是否去掉 SQL 中的空格 if (configuration.isShrinkWhitespacesInSql()) { sql = parser.parse(removeExtraWhitespaces(originalSql)); } else { // 在这里将 #{} 占位符 替换为 ?,并由 ParameterMappingTokenHandler 解析参数映射信息 sql = parser.parse(originalSql); } // 最终参数映射信息会保存在 StaticSqlSource 中 return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); } } ``` `ParameterMappingTokenHandler` 是 `SqlSourceBuilder` 中的 **内部类**,它同样实现了 `TokenHandler` 接口,也是一个解析工具类,与上文提到过的 `GenericTokenParser` 解析器组合使用。它会将占位符中的信息解析为 `ParameterMapping` 信息,并将占位符替换为 ? (`handleToken` 方法): ```java private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler { // 参数映射信息 private final List<ParameterMapping> parameterMappings = new ArrayList<>(); private final Class<?> parameterType; private final MetaObject metaParameters; public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) { super(configuration); this.parameterType = parameterType; this.metaParameters = configuration.newMetaObject(additionalParameters); } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } @Override public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } private ParameterMapping buildParameterMapping(String content) { // 解析表达式中的参数映射信息 Map<String, String> propertiesMap = parseParameterMapping(content); // 参数字段 String property = propertiesMap.get("property"); // ... 处理 parameterType 配置 // ... 处理占位符参数中标记的 javaType, jdbcType 等配置 // ... 封装 TypeHandler } } ``` `ParameterMappingTokenHandler#buildParameterMapping` 方法会对参数映射信息进行封装,实际的占位符中表达式的解析是在 `parseParameterMapping` 中完成的,会继续调用到 `ParameterExpression` 的构造方法完成解析,`ParameterExpression` 继承了 `HashMap`,会将解析结果以键值对的形式保存: ```java public class ParameterExpression extends HashMap<String, String> { private static final long serialVersionUID = -2417552199605158680L; public ParameterExpression(String expression) { parse(expression); } private void parse(String expression) { int p = skipWS(expression, 0); if (expression.charAt(p) == '(') { // org/apache/ibatis/builder/SqlSourceBuilder.java:140,expression 表达式内容暂不支持,忽略这段逻辑 expression(expression, p + 1); } else { // 只关注这里即可 property(expression, p); } } // ... } ``` 只需关注 `property` 方法即可,它的作用是对占位符表达式中的 `javaType, jdbcType, mode, numericScale, resultMap, typeHandler, jdbcTypeName` 属性进行解析,其中常用常见的是 `javaType,jdbcType`,示例 SQL 会被解析为如下内容并保存下来: ```json { "property": "id", "jdbcType": "INTEGER", "javaType": "int" } ``` 大家可以看一下 `ParameterExpression` 的具体实现,该类的实现遵循了《代码整洁之道》中 **每个方法只做一件事,且方法足够短小** 的原则,并将方法自上而下排列,读这个类的代码就像读报纸一样,被依赖次数越多的方法越靠下,这也暗示 **越靠近类下方的方法越通用**。 SQL 中占位符被替换为 `?`,结果如下: ```sql select id, username, password, email, bio, favourite_section from author where id = ? ``` 最终会被保存在 `StaticSqlSource` 中的 `sql` 字段中,表示封装的是静态 SQL: ```java public class StaticSqlSource implements SqlSource { // 替换完成 #{} 占位符的 SQL 内容 private final String sql; // 已经解析完成的 ParameterMapping 对象 private final List<ParameterMapping> parameterMappings; private final Configuration configuration; // ... } ``` 现在 `RawSqlSource` 的构造方法已执行完毕,其内部组合的 `SqlSource` 对象为 `StaticSqlSource`,至此 `MappedStatement` 中 `SqlSource` 对象的解析便完毕了。 ```java public class RawSqlSource implements SqlSource { // StaticSqlSource private final SqlSource sqlSource; // ... } ``` ### 动态 SQL 的解析 接下来我们看一个带有动态标签的 SQL 是如何解析的,以 `org.apache.ibatis.session.SqlSessionTest#dynamicSqlParse` 为例,它的 SQL 如下: ```xml <select id="selectAuthor" parameterMap="selectAuthor" resultMap="selectAuthor"> select id, username, password, email, bio, favourite_section from author <where> <if test="id != null"> id = #{id} </if> </where> </select> ``` 它会被分成两部分,分别为: ```xml <select id="selectAuthor" parameterMap="selectAuthor" resultMap="selectAuthor"> select id, username, password, email, bio, favourite_section from author </select> ``` ```xml <where> <if test="id != null"> id = #{id} </if> </where> ``` 对于第一部分的处理与上述静态 SQL 解析的过程一致,不再赘述。第二部分是处理包含动态标签 `<where>` 和 `<if>` 的内容,它会执行到 `XMLScriptBuilder#parseDynamicTags` 方法的处理 `ELEMENT_NODE` 的逻辑中: ```java public class XMLScriptBuilder extends BaseBuilder { // ... protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // ... } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // 根据 node 节点名称选择对应的 Handler String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } // 指定对应的处理方法 handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); } } ``` 它会根据标签类型获取到对应的 `NodeHandler`,执行相应的处理方法。其中部分 `Handler` 会再调用到 `XMLScriptBuilder#parseDynamicTags` 方法,出现递归调用,不过具体的执行过程相对简单,但也需要自己 Debug 跟踪验证。 上述 SQL 被解析完成后,`SqlNode` 表示为: ![sqlNode2.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-12-15-14-31bEWjOkF15F2QQcUb.png) 再回到 `XMLScriptBuilder#parseScriptNode` 方法: ```java public class XMLScriptBuilder extends BaseBuilder { // ... public SqlSource parseScriptNode() { // 将 xml 文件中定义的 SQL 解析成不同的 SqlNode 对象,并都记录在 MixedSqlNode 对象中 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { // 包含动态标签的 SQL sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { // ... } return sqlSource; } } ``` 动态 SQL 会被标记为 `isDynamic == true`,最终会被解析成 `DynamicSqlSource` 对象,如下是它的构造方法: ```java public class DynamicSqlSource implements SqlSource { private final Configuration configuration; private final SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { this.configuration = configuration; this.rootSqlNode = rootSqlNode; } // ... } ``` 非常简单,仅仅是赋值操作,因为它是动态 SQL,会根据入参生成 SQL 信息。到这里,动态 SQL 的解析操作也完成了。下面我们来看一下创建 `MappedStatement` 使用到的 **建造者模式**: ### 建造者模式 在 `org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode` 方法执行 `org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement` 逻辑创建 `MappedStatement` 对象时使用了 **建造者模式**: ```java public void parseStatementNode() { // 定义相关字段的逻辑... // 方法的入参非常多,之所以能这么写,放弃可复用性,那么该方法不会轻易发生变更 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect); } ``` 实现该模式对应的类图关系如下: ![MappedStatement 建造者模式.drawio.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-12-15-12-39lnWSAEbF12CuDf12K.png) * `MapperBuilderAssistant`: 对应建造者模式中的 `Director`,其中定义了构建 `MappedStatement` 对象各个字段值的逻辑 * `MappedStatement`: 要构建的对象本身 * `Builder`: 是 `MapperStatement` 中的静态内部类,定义了链式编程赋值的方法,用于构建 `MappedStatement` 对象 建造者模式适用于构建字段较多的复杂对象的场景,这样便能将构造 `MappedStatement` 的复杂逻辑隐藏起来。定义内部类 `MappedStatement.Builder` 并支持链式编程,是使用建造者模式时常见写法。 至此,创建 `MappedStatement` 中的要点和 `SqlSource` 的介绍已经完毕了。
上一篇:由 Mybatis 源码畅谈软件设计(一):序
下一篇:记录一次「OSS上传文件的前置处理机制」实例剖析
wy****
文章数
33
阅读量
2983
作者其他文章
01
高性能MySQL实战(一):表结构
最近因需求改动新增了一些数据库表,但是在定义表结构时,具体列属性的选择有些不知其所以然,索引的添加也有遗漏和不规范的地方,所以我打算为创建一个高性能表的过程以实战的形式写一个专题,以此来学习和巩固这些知识。1. 实战我使用的 MySQL 版本是 5.7,建表 DDL 语句如下所示:根据需求创建 接口调用日志 数据库表,请大家浏览具体字段的属性信息,它们有不少能够优化的点。CREATE TABLE
01
分布式服务高可用实现:复制
1. 为什么需要复制我们可以考虑如下问题:当数据量、读取或写入负载已经超过了当前服务器的处理能力,如何实现负载均衡?希望在单台服务器出现故障时仍能继续工作,这该如何实现?当服务的用户遍布全球,并希望他们访问服务时不会有较大的延迟,怎么才能统一用户的交互体验?这些问题其实都能通过 “复制” 来解决:复制,即在不同的节点上保存相同的副本,提供数据冗余。如果一些节点不可用,剩余的节点仍然可以提供数据服务
01
高性能MySQL实战(三):性能优化
这篇主要介绍对慢 SQL 优化的一些手段,而在讲解具体的优化措施之前,我想先对 EXPLAIN 进行介绍,它是我们在分析查询时必要的操作,理解了它输出结果的内容更有利于我们优化 SQL。为了方便大家的阅读,在下文中规定类似 key1 的表示二级索引,key_part1 表示联合索引的第一部分,unique_key1 则表示唯一二级索引,primary_key 表示主键索引。高性能MySQL实战(一
01
从2PC和容错共识算法讨论zookeeper中的Create请求
最近在读《数据密集型应用系统设计》,其中谈到了zookeeper对容错共识算法的应用。这让我想到之前参考的zookeeper学习资料中,误将容错共识算法写成了2PC(两阶段提交协议),所以准备以此文对共识算法和2PC做梳理和区分,也希望它能帮助像我一样对这两者有误解的同学。1. 2PC(两阶段提交协议)两阶段提交 (two-phase commit) 协议是一种用于实现 跨多个节点的原子事务(分布
wy****
文章数
33
阅读量
2983
作者其他文章
01
高性能MySQL实战(一):表结构
01
分布式服务高可用实现:复制
01
高性能MySQL实战(三):性能优化
01
从2PC和容错共识算法讨论zookeeper中的Create请求
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号