MySQL 性能优化浅析及线上案例

作者:京东健康 孟飞

1、 数据库性能优化的意义

业务发展初期,数据库中量一般都不高,也不太容易出一些性能问题或者出的问题也不大,但是当数据库的量级达到一定规模之后,如果缺失有效的预警、监控、处理等手段则会对用户的使用体验造成影响,严重的则会直接导致订单、金额直接受损,因而就需要时刻关注数据库的性能问题。

2、 性能优化的几个常见措施

数据库性能优化的常见手段有很多,比如添加索引、分库分表、优化连接池等,具体如下:

| 序号 | 类型 | 措施 | 说明 | | 1 | 物理级别 | 提升硬件性能 | 将数据库安装到更高配置的服务器上会有立竿见影的效果,例如提高 CPU 配置、增加内存容量、采用固态硬盘等手段,在经费允许的范围可以尝试。 | | 2 | 应用级别 | 连接池参数优化 | 我们大部分的应用都是使用连接池来托管数据库的连接,但是大部分都是默认的配置,因而配置好超时时长、连接池容量等参数就显得尤为重要。 1、 如果链接长时间被占用,新的请求无法获取到新的连接,就会影响到业务。 2、 如果连接数设置的过小,那么即使硬件资源没问题,也无法发挥其功效。之前公司做过一些压测,但就是死活不达标,最后发现是由于连接数太小。 | | 3 | 单表级别 | 合理运用索引 | 如果数据量较大,但是又没有合适的索引,就会拖垮整个性能,但是索引是把双刃剑,并不是说索引越多越好,而是要根据业务的需要进行适当的添加和使用。 缺失索引、重复索引、冗余索引、失控索引这几类情况其实都是对系统很大的危害。 | | 4 | 库表级别 | 分库分表 | 当数据量较大的时候,只使用索引就意义不大了,需要做好分库分表的操作,合理的利用好分区键,例如按照用户 ID、订单 ID、日期等维度进行分区,可以减少扫描范围。 | | 5 | 监控级别 | 加强运维 | 针对线上的一些系统还需要进一步的加强监控,比如订阅一些慢 SQL 日志,找到比较糟糕的一些 SQL,也可以利用业务内一些通用的工具,例如 druid 组件等。 |

3、 MySQL 底层架构

首先了解一下数据的底层架构,也有助于我们做更好优化。

一次查询请求的执行过程

我们重点关注第二部分和第三部分,第二部分其实就是 Server 层,这层主要就是负责查询优化,制定出一些执行计划,然后调用存储引擎给我们提供的各种底层基础 API,最终将数据返回给客户端。

4、MySQL 索引构建过程

目前比较常用的是 InnoDB 存储引擎,本文讨论也是基于 InnoDB 引擎。我们一直说的加索引,那到底什么是索引、索引又是如何形成的呢、索引又如何应用呢?这个话题其实很大也很小,说大是因为他底层确实很复杂,说小是因为在大部分场景下程序员只需要添加索引就好,不太需要了解太底层原理,但是如果了解不透彻就会引发线上问题,因而本文平衡了大家的理解成本和知识深度,有一定底层原理介绍,但是又不会太过深入导致难以理解。

首先来做个实验:

创建一个表,目前是只有一个主键索引

CREATE TABLE `t1`(

a int NOT NULL,

b int DEFAULT NULL,

c int DEFAULT NULL,

d int DEFAULT NULL,

e varchar(20) DEFAULT NULL,

PRIMARYKEY(a)

)ENGINE=InnoDB

插入一些数据:

insert into test.t1 values(4,3,1,1,'d');

insert into test.t1 values(1,1,1,1,'a');

insert into test.t1 values(8,8,8,8,'h');

insert into test.t1 values(2,2,2,2,'b');

insert into test.t1 values(5,2,3,5,'e');

insert into test.t1 values(3,3,2,2,'c');

insert into test.t1 values(7,4,5,5,'g');

insert into test.t1 values(6,6,4,4,'f');

MYSQL 从磁盘读取数据到内存是按照一页读取的,一页默认是 16K,而一页的格式大概如下。

每一页都包括了这么几个内容,首先是页头、其次是页目录、还有用户数据区域。

1)刚才插入的几条数据就是放到这个用户数据区域的,这个是按照主键依次递增的单向链表。

2)页目录这个是用来指向具体的用户数据区域,因为当用户数据区域的数据变多的时候也就会形成分组,而页目录就会指向不同的分组,利用二分查找可以快速的定位数据。

当数据量变多的时候,那么这一页就装不下这么多数据,就要分裂页,而每页之间都会双向链接,最终形成一个双向链表。

页内的单向链表是为了查找快捷,而页间的双向链表是为了在做范围查询的时候提效,下图为示意图,其中其二页和第三页是复制的第一页,并不真实。

而如果数据还继续累加,光这几个页也不够了,那就逐步的形成了一棵树,也就是说索引 B-Tree 是随着数据的积累逐步构建出来的。

最下边的一层叫做叶子节点,上边的叫做内节点,而叶子节点中存储的是全量数据,这样的树就是聚簇索引。一直有同学的理解是说索引是单独一份而数据是一份,其实 MySQL 中有一个原则就是数据即索引、索引即数据,真实的数据本身就是存储在聚簇索引中的,所谓的回表就是回的聚簇索引。

但是我们也不一定每次都按照主键来执行 SQL 语句,大部分情况下都是按照一些业务字段来,那就会形成别的索引树,例如,如果按照 b,c,d 来创建的索引就会长这样。

推荐 1 个网站,可以可视化的查看一些算法原型:

目录:

http://www.cs.usfca.edu/~galles/visualization/Algorithms.html

B + 树

http://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

而在 MySQL 官网上介绍的索引的叶子节点是双向链表。

关于索引结构的小结:

对于 B-Tree 而言,叶子节点是没有链接的,而 B+Tree 索引是单向链表,但是 MySQL 在 B+Tree 的基础之上加以改进,形成了双向链表,双向的好处是在处理 > <,between and 等 ' 范围查询 ' 语法时可以得心应手。

5、MySQL 索引的一些使用规范

1、 只为用于搜索、排序或分组的列创建索引。

重点关注 where 语句后边的情况

2、 当列中不重复值的个数在总记录条数中的占比很大时,才为列建立索引。

例如手机号、用户 ID、班级等,但是比如一张全校学生表,每条记录是一名学生,where 语句是查询所有’某学校‘的学生,那么其实也不会提高性能。

3、 索引列的类型尽量小。

无论是主键还是索引列都尽量选择小的,如果很大则会占据很大的索引空间。

4、 可以只为索引列前缀创建索引,减少索引占用的存储空间。

alter table single_table add index idx_key1(key1(10))

5、 尽量使用覆盖索引进行查询,以避免回表操作带来的性能损耗。

select key1 from single_table order by key1

6、 为了尽可能的少的让聚簇索引发生页面分裂的情况,建议让主键自增。

7、 定位并删除表中的冗余和重复索引。

冗余索引:

单列索引:(字段 1)

联合索引:(字段 1 字段 2)

重复索引:

在一个字段上添加了普通索引、唯一索引、主键等多个索引

6、 执行计划

其中常用的是:

possible_keys: 可能用到的索引

key: 实际使用的索引

rows:预估的需要读取的记录条数

7、 线上案例

案例 1:

在建设互联网医院系统中,问诊单表当时量级 23 万左右,其中有一个 business_id 字符串字段,这个字段用来记录外部订单的 ID,并且在该字段上也加了索引,但是 ' 根据该 ID 查询详情 ' 的 SQL 语句却总是时好时坏,性能不稳定,快则 10ms,慢则 2 秒左右,SQL 大体如下:

select 字段 1、字段 2、字段 3 from nethp_diag where business_Id = ?

因为 business_id 是记录第三方系统的订单 ID,为了兼容不同的第三方系统,因而设计成了字符串类型,但如果传入的是一个数字类型是无法使用索引的,因为 MySQL 只能将字符串转数字,而不能将数字转字符串,由于外部的 ID 有的是数字有的是字符串,因而导致索引一会可以走到,一会走不到,最终导致了性能的不稳定。

案例 2:

在某次大促的当天,突然接到 DBA 运维的报警,说数据库突然流量激增,CPU 也打到 100% 了,影响了部分线上功能和体验,遇到这种情况当时大部分人都比较紧张,下图为当时的数据库流量情况:

相关 SQL 语句:

<!-- 统计医患下过去 24 小时内开的电子病历总数 -->

<select id="getCountByDPAndTime" resultType="integer">

select count(1)

from jdhe_medical_record

where status = 1 and is_test = #{isTest,jdbcType=INTEGER} and electric_medical_record_status in (2,3)

<if test="patientId != null">

and patient_id = #{patientId,jdbcType=BIGINT}

</if>

<if test="doctorPin != null">

and doctor_pin = #{doctorPin,jdbcType=VARCHAR}

</if>

and created >#{dateStart,jdbcType=TIMESTAMP};

</select>

当时的索引情况

当时的执行计划

其实在 patientId 和 doctor_pin 两个字段上是有索引的,但是由于线上情况的改变,导致 test 判断没有进入,这样的通用查询导致这两个字段没有设置上,进而导致了数据库扫描的量激增,对数据库产生了很大压力。

案例 3:

2020 年某日上午收到数据库 CPU 异常报警,对线上有一定的影响,后续检查数据库 CPU 情况如下,从 7 点 51 分开始,CPU 从 8% 瞬间达到 99.92%,丝毫没有给程序员留任何情面。

当时的 SQL 语句:

select rx_id, rx_create_time from nethp_rx_info where rx_status = 5 and status = 1 and rx_product_type = 0 and (parent_rx_id = 0 or parent_rx_id is null) and business_type != 7 and vender_id = 8888 order by rx_create_time asc limit 1;

当时的索引情况:

PRIMARY KEY (`id`), UNIQUE KEY `uniq_rx_id` (`rx_id`), KEY `idx_diag_id` (`diag_id`), KEY `idx_doctor_pin` (`doctor_pin`) USING BTREE, KEY `idx_rx_storeId` (`store_id`), KEY `idx_parent_rx_id` (`parent_rx_id`) USING BTREE, KEY `idx_rx_status` (`rx_status`) USING BTREE, KEY `idx_doctor_status_type` (`doctor_pin`, `rx_status`, `rx_type`), KEY `idx_business_store` (`business_type`, `store_id`), KEY `idx_doctor_pin_patientid` (`patient_id`, `doctor_pin`) USING BTREE, KEY `idx_rx_create_time` (`rx_create_time`)

当时这张表量级 2000 多万,而当这条慢 SQL 执行较少的时候,数据库的 CPU 也就下来了,恢复到了 49.91%,基本可以恢复线上业务,从而表象就是线上间歇性的一会可以开方一会不可以,这条 SQL 当时总共执行了 230 次,当时的 CPU 情况也是忽高忽低,伴随这条 SQL 语句的执行情况,从而最终证明 CPU 的飙升是由于这条慢 SQL。当线上业务逻辑复杂的时候,你很难第一时间知道到底是由于那条 SQL 引起的,这个就需要对业务非常熟悉,对 SQL 很熟悉,否则就会白白浪费大量的排查时间。

最后的排查结果:

在头天晚上的时候添加了一条索引 rx_create_time,当时没事,但是第二天却出了事故。

加索引前后走的索引不同,一个是走的 rx_status(处方审核状态)单列索引,一个是走的 rx_create_time (处方提交事件) 单列索引,这个就要回到业务,因为处方状态是个枚举,且枚举范围不到 10 个,也就说线上 29,000,000 的数据量也就是被分成了不到 10 份,rx_status=5 的值是其中一份,因而通过这个索引就可以命中很多行,这是业务规则,再套用 MySQL 的特性,主要是以下几条:

1、没加新索引 rx_create_time 的时候,由于 order by 后边没有索引,就看 where 条件中是否有合适的索引,查询选择器选定 rx_status 这个单列索引,而 rx_status=5 这个条件下限制的数据行在索引中是连续,即使需要的 rx_id 不在索引中,再回主键聚簇索引也来得及,由于 order by 后边没有索引,所以走磁盘级别的排序 filesort,高峰积压的时候处方就 1 万到 2 万,跑到了 100ms, 白天低谷的时候几百单也就 20ms。

2、新加索引之后,就分两种情况:

2.1、加索引是在晚上,当前命中的行数比较少,由于当天晚上的时候待审核的处方确实很少,也就是 rx_status=5 的确实很少,查询优化器感觉反正没多少行,排序不重要,因而就还是选择 rx_status 索引。

2.2、第二天白天,待审核的处方数量很多了(rx_status=5 的数据量多了),当时可以命中几万数据,如果当前命中的行数比较多,查询优化器就开始算成本,感觉排序的成本会更高,那就优先保排序吧,所以就选择 rx_create_time 这个字段,但是这个索引树上没有别的索引字段的信息,没办法,几乎每条数据都要回表,进而引发了灾难。

8、 推荐用书

这本书以一种诙谐幽默的风格写了 MySQL 的一些运行机制,非常适合阅读,理解成本大幅降低。

http://item.jd.com/13009316.html

http://item.jd.com/10066181997303.html

9、一些感悟

关于数据库的性能优化其实是一个很复杂的大课题,很难通过一篇帖子讲的很全面和深刻,这也就是为什么我的标题是‘浅析’,程序员的成长一定是要付出代价和成本,因为只有真的在一线切身体会到当时的紧张和压力,对于一件事情才能印象深刻,但反之也不能太过于强调代价,如果可以通过一些别人的分享就可以规避一些自己业务的问题和错误的代价也是好的。



    Austin Liu  刘恒辉
    Lzhdim Group's Chairman,Project Manager and Software Designer
    E-Mail:lzhdim@163.com
    Blog:  http://lzhdim.cnblogs.com

    欢迎收藏和转载此博客中的博文,但是请注明出处,给作者一个与大家交流的空间。谢谢大家。
本文转载于网络 如有侵权请联系删除

相关文章

  • Python一时爽,优化两行泪

    之前做时间性能优化时,封装了一个Python并行的函数: fromconcurrentimportfutures defconc_map(func,ls_data,max_workers=None): """并发执行 一个进程执行ls_data中的一个数据 :paramfuncfunction需要执行的函数 :paramls_datalist列表数据 :parammax_workersint|None最大的worker数量,默认None则根据cpu的核数来定 """ withfutures.ThreadPoolExecutor(max_workers=max_workers)asexecutor: results=executor.map(func,ls_data) returnlist(results)复制想要达到的效果就是可以并行多个线程来实现时间性能的加速。开始时的测试代码如下: deftask(i): time.sleep(0.1) deftest(): start=time.time() task(1) prin

  • 企业架构方法论可以简化吗?

    在与很多读者朋友的沟通中,经常会遇到对方法论的各种思考和提问,这都是为了推动方法论的进步,今天跟大家聊下问的最多的一个,也许笔者自己说的也是误解,大家共同讨论吧。方法论能简化吗?这个问题估计是对企业架构方法论的各类提问中最“网红”的选手,几乎所有人在学习、谈论企业架构的过程中都问过这件事,很多人也都尝试过各种改良,但是,从方法论的角度来讲,笔者觉得,能简化的并不是它的过程,而是深度。首先,打个不恰当的比方,要求简化方法论,其实有点儿像跟大夫说,您能不看病直接给笔者开药吗?吃了药不休息直接出去玩行吗?都行,前边那个是大夫不想干了,后边那个是你自己胆子大。开玩笑地讲,你的身体你清楚,你自己负责吧。企业架构方法论也类似,想想大名鼎鼎的TOGAF,自从2002年第八版以来,确实总体变化有限,而且更新频率差不多“十年磨一剑”的感觉。第八版以前可是“一年磨一剑”,为啥能那么快?因为不完善呗,从1995年搞到2002年,总算比较完善了,所以速度也明显降下来了。OpenGroup的年度大会一直在开,每年都有很好的经验出来分享,但是再更新方法论显然不像以前那么容易了。一些必要的基本环节确实省不下去,比如

  • DDD领域驱动设计实战电商活动中心重构

    现实的业务,非常复杂。即使同一事物,在多个业务下意义可能完全不同。比如【商品】:在商品详情页语境指【商品基本信息】在下单页语境指【购买项】在物流页面语境又是【被运送的货物】DDD核心思想就是让正确的领域模型发挥作用。DDD指导开发将不同子业务单元划分为不同子领域,在各个子领域内部分别建模应对业务的复杂性。1背景经典的MVC开发,因为初期业务也比较简单,为光速上线,综合考虑成本和风险,经常创建一个大模型,各个模块都想着直接复用这同一模型。但随业务发展,各子领域的逻辑越来越复杂,对该大模型的修改就会变成一种灾难,有时明明是要改一个A子领域的逻辑,却莫名其妙影响到了B或者C子领域的线上功能,这也是导致代码中出现一堆ifelse的一大源泉。比如活动中心,主导商品的优惠活动管理、计算不同用户的优惠结果。【商品管理】和【活动管理】作为两个不同业务单元,在初期被设计为一个大的商品模型,由商品模块统一管理。 原设计之殇但随业务发展,活动形式推陈出新,业务形态多样,需求也越来越个性化,导致如下问题日渐明显:功能不够灵活活动信息作为商品信息的一个属性在商品管理模块配置。 比如为了引导用户使用App需要设置

  • Cypress web自动化31-request发post请求登录接口

    前言cypress不仅可以用浏览器访问web页面,也可以直接发request请求访问接口。 在实际工作中,很多时候都需要先登录,如果只是写登录页面的案例,可以直接在web页面操作。 如果是写其他页面的案例,需要依赖登录,这时候应该是不需要再次重复打开页面去登录,正确的做法是在用例跑之前写个前置,发登录的请求,保存cookie,让页面保持登录状态。登录接口以禅道网站为例,登录的接口没提供接口文档的话,可以自己抓包获取接口请求报文 使用fiddler抓包,获取请求报告信息POSThttp://localhost:8080/zentao/user-login.htmlHTTP/1.1 X-Requested-With:XMLHttpRequest User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/83.0.4103.61Safari/537.36 Content-Type:application/x-www-form-urlencoded;charset=UTF-

  • 成功实施自动化测试的优点

    本文内容是群友在探讨“自动化价值”时候一些观点,整理加工,以供参考。随着技术的发展,保证应用程序的质量变得越来越具有挑战性。由于敏捷开发和成本因素,导致了发现问题窗口时间有限,因此测试经常会忽略某些应该关注的地方。测试工程师应该在发布产品之前发现其中存在的问题,但是任何软件都不可能是完美的!发现问题后,敏捷开发模式的做法通常是在生产后部署快速修复程序,然后再次进行回归测试。整体回归的测试而言非常耗时,很多时候是无法确定修改部分功能导致的影响范围到底多大。在这种情况下,很可能陷入了恶性循环。与手动测试相反,自动化测试是提高测试过程的效率和覆盖范围的有效方法。它的出现是为了加快执行周期,使测试人员免于陷入重复性任务,减少人工工作并提供即时反馈。什么是自动化测试?自动化测试是使用自动化工具来对应用软件执行测试用例和检验响应功能是否符合产品设计的过程。测试工具获取实际结果,并将其与预期结果进行比较,以生成详细的测试报告。自动化测试的好处下面是群友在探讨“自动化价值”时候一些观点,收集整理,以供参考。测试执行7*24与手动测试相比,自动化测试的主要好处之一是可以随时随地从任何地方执行测试的灵活性

  • # 使用InheritedWidget传递数据

    #使用InheritedWidget传递数据除了StatefulWidget、StatelessWidget之外flutter还提供了另外一个用的Widget组件即InheritedWidget。那么该组件的作用时什么呢?复制#我们来看一下数据是如何从父widget传递到子widget的下面我们定义一个嵌套三层的数据传递例子:classDataTransferAWidgetextendsStatelessWidget{ finalintdata; DataTransferAWidget(this.data); @override Widgetbuild(BuildContextcontext){ returnContainer( alignment:Alignment.center, child:Column( mainAxisAlignment:MainAxisAlignment.center, children:<Widget>[ Expanded(child:Text("A:${data.toString()}")), Expanded( //子组

  • 国产ros小车及相关文档

    http://wiki.ros.org/cn/xiaoqian机器人参数教程http://wiki.ros.org/Robots/Roch2roch文档RochROSSoftwareMaintainer:SawYer-RoboticsRoch(robotplatformforcommecialandhome)integratedmultipledifferentRGBDcamerasandlidarslikeMicrosoft'sKinect,Asus'XtionPro,Intel'Realsense200andSlamtec'RplidarA1/A2etc.Mainlyusedforbusinessandfamily.ContentsRochConfigureYourRochBringupTeleoperationNavigationSimulationOtherthingsFullinstallationOptionalinstallationInstallationTutorialsInstallationTherehavetwooptio

  • 如何使用 Docker 搭建 Java Web 运行环境

    Docker是2014年最为火爆的技术之一,几乎所有的程序员都听说过它。Docker是一种“轻量级”容器技术,它几乎动摇了传统虚拟化技术的地位,现在国内外已经有越来越多的公司开始逐步使用Docker来替换现有的虚拟化平台了。作为一名Java程序员,我们是时候一起把Docker学起来了!本文会对虚拟化技术与Docker容器技术做一个对比,然后引出一些Docker的名词术语,比如:容器、镜像等,随后将使用Docker搭建一个JavaWeb运行环境,最后将对本文做一个总结。我们先来回顾一下传统虚拟化技术的体系架构:可见,我们在宿主机的操作系统上,可安装了多个虚拟机,而在每个虚拟机中,通过虚拟化技术,实现了一个虚拟操作系统,随后,就可以在该虚拟操作系统上,安装自己所需的应用程序了。这一切看似非常简单,但其中的技术细节是相当高深莫测的,大神级人物都不一定说得清楚。凡是使用过虚拟机的同学,应该都知道,启动虚拟机就像启动一台计算机,初始化过程是相当慢的,我们需要等很久,才能看到登录界面。一旦虚拟机启动以后,就可以与宿主机建立网络连接,确保虚拟机与宿主机之间是互联互通的。不同的虚拟机之间却是相互隔离的

  • 分段锁的原理

    前言:在分析ConcurrentHashMap的源码的时候,了解到这个并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。 在并发程序中,串行操作是会降低可伸缩性,并且上下文切换也会减低性能。在锁上发生竞争时将通水导致这两种问题,使用独占锁时保护受限资源的时候,基本上是采用串行方式—-每次只能有一个线程能访问它。所以对于可伸缩性来说最大的威胁就是独占锁。我们一般有三种方式降低锁的竞争程度: 1、减少锁的持有时间 2、降低锁的请求频率 3、使用带有协调机制的独占锁,这些机制允许更高的并发性。在某些情况下我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这成为分段锁。其实说的简单一点就是:容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。比如:在Concurre

  • 区间dp入门_状压dp

    大家好,又见面了,我是你们的朋友全栈君。一.什么是区间dp?顾名思义:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的最优解进而得出整个大区间上最优解的dp算法。 二.核心思路既然让我求解在一个区间上的最优解,那么我把这个区间分割成一个个小区间,求解每个小区间的最优解,再合并小区间得到大区间即可。所以在代码实现上,我可以枚举区间长度len为每次分割成的小区间长度(由短到长不断合并),内层枚举该长度下可以的起点,自然终点也就明了了。然后在这个起点终点之间枚举分割点,求解这段小区间在某个分割点下的最优解。板子如下: for(intlen=1;len<=n;len++){//枚举长度 for(intj=1;j+len<=n+1;j++){//枚举起点,ends<=n intends=j+len-1; for(inti=j;i<ends;i++){//枚举分割点,更新小区间最优解 dp[j][ends]=min(dp[j][ends],dp[j][i]+dp[i+1][ends]+something); } } }复制三.朴素区间dp(n

  • 13.spring源码之ConfigurationClassPostProcessor类概括及其BeanDefintion的生成

    前面的文章一些讲了xml解析、在xml中添加扫描组件就会把@Component类型注解的类封装成BeanDefinition,但现在xml文件的配置方法慢慢的退出了历史的舞台,大多都用注解开发,那么注解又是怎么完成对BeanDefinition的封装的呢?是通过ConfigurationClassPostProcessor类完成的,ConfigurationClassPostProcessor类实现了BeanDefintionRegistryPostProcessor类,在postProcessorBeanFactory()方法中完成了对@Configuration注解的解析,在postProcessorBeanDefinitionRegistr()方法中完成了对@Conditional、@ComponentScan、@Component、@PropertySource、@Import、@ImportSource、@Bean等注解的解析,流程图如下: 那么这个类又是在什么地方生产BeanDefiniton的呢?我们来看上下文注解对象 1.创建一个类添加ComponnetScan注解

  • Angular报错:Error: Unknown argument: spec

    解决方案 使用--skip-tests代替 效果展示 可以看到spec.ts消失了 参考链接 https://stackoverflow.com/questions/62228834/angular-cli-command-issue-unknown-option-spec 学以致用,知行合一

  • js获取浏览器类型

    functionadd(){ varuserAgent=navigator.userAgent, rMsie=/(msie\s|trident.*rv:)([\w.]+)/, rFirefox=/(firefox)\/([\w.]+)/, rOpera=/(opera).+version\/([\w.]+)/, rChrome=/(chrome)\/([\w.]+)/, rSafari=/version\/([\w.]+).*(safari)/; varbrowser; varversion; varua=userAgent.toLowerCase(); varstu_name; functionuaMatch(ua){ varmatch=rMsie.exec(ua); if(match!=null){ alert("IE"); stu_name=$("#stu_name").text().split("\n"); alert(stu_name);            复制 varSys={};varua=navigator.userAgent.toLowerCase();vars;

  • Redis集群的三种模式

    Redis是什么 Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSIC编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性: ·        基于内存运行,性能高效 ·        支持分布式,理论上可以无限扩展 ·        key-value存储系统 ·        开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API   Ⅰ-主从(master-salver)2.8之前 redis同mysql一样,虽然读写都很快,但是也会产生读写压力大的情况。为了分担读的压力,Redis支持主从复制,master进

  • 学习php的步骤是什么?

    PHP应该学什么,如何学好PHP (注:原文来自传智播客) 一些共性问题,大致是: 1.应该怎样学习PHP,学习的顺序是怎样的? 2.PHP学好后,可以做什么事情? 3.听得懂课,但是一旦自己独立写程序,就没有思路? 4.学习PHP需要怎样的基础,算法重要吗? 首先,大家要达成一个共识,就是学习任何一门编程技术都需要我们付出心血,都要讲求循序渐进,由浅入深。对每一个知识点要搞透,然后通过案例来加深认识,最后还需要把学习到的各个知识点实际运用到项目中去,才能融会贯通,最后才能到达能自如的驾驭项目的水平,现在我们就来看看应该怎样一步一步的学习PHP这门编程技术,先看看学习PHP的顺序图,此学习顺序图是我推荐的学习内容和学习顺序(除了这里列出的内容,要走向高手之路肯定还有更多技术需要学习,我这里只是对于初学者列出的主干学习内容,并不是全部): PHP应该学什么,如何学好PHP(一) 对于PHP初学者来说,看到这样多的内容,心里已经开始打退堂鼓了,但是,万丈高楼平地起,我们只要坚持每天学习一部分内容,最后总会把这些技术都精通掌握的。 PHP这门技术相对java和.net来说,是比较简单

  • 11.3 校内模拟赛

    总结: 祭考试做出一道期望dp/se 考试心路历程: 5min读完T1,ahhhhhh,签到题,10min写完,一发过大样例。 8:20开干T2,期望dp,读完题,感觉可做。 写了好久,调过样例,写了对拍,一拍就挂,rnm。 调半天,转移写挂== 10:10开T3 T3题目好奇怪,为啥开头是T2的题目啊,没用,没用,删了。 艹,这花店在哪儿,我家在哪儿,我咋找不到我家!! 读了好几遍,没读懂题,麻了,睡觉。 赛后:为啥我T3题目少了一个自然段啊!!!! T1三向城 题面 solution 手摸题目,你发现这是个满二叉树。 然后你会发现,他的层数其实很少。 你还会发现每个节点的父亲就是改节点/2 然后你就暴力向上跳就好了== code #include<bits/stdc++.h> #defineintlonglong usingnamespacestd; constintMAX=35; intread(){ intx=0,f=1;charc=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} w

  • Janus安装教程,ubuntu18.04系统

    Janus安装教程,ubuntu18.04系统   本文介绍Jansu如何安装,操作系统为Ubuntu18.04。    (1)安装git 执行命令:“sudoapt-getinstallgit”。   (2)安装aptitude 执行命令:“sudoapt-getinstallaptitude”。   (3)安装依赖 第一步: 执行命令:“sudoaptitudeinstalllibmicrohttpd-devlibjansson-dev\     libssl-devlibsrtp-devlibsofia-sip-ua-devlibglib2.0-dev\     libopus-devlibogg-devlibcurl4-openssl-devliblua5.3-dev\     libconfig-devpkg-configgengetoptlibtoolautomake”。 (“”内为一

  • 获取数组中最长的那一项, 并且打印其位置和长度

    vararr=['2sfw7ry2gfsgrqew7ruwer2','342','fasfe3','f45wr','fwds','yufg','53gfdgfsd3gd','f23sffd','fds','543dgdsyg'] varfn=function(arr){ letmaxIndex=0 letlong=0 letvalue='' for(leti=0;i<arr.length;i++){ lettemp=arr[i].length if(temp>=long){ long=temp maxIndex=i value=arr[i] } } console.log('最长的是:'+value+"长度是:"+long+'位置是:'+maxIndex) } fn(arr)复制  

  • JavaScript 核心原理精讲

    JS数据类型   基础类型:undefined、Null、Boolean、String、Number、Symbol、BigInt。(存储在栈内存)   引用类型:Array、Function、Object、RegExp、Date、Math。(存储在堆内存) JS类型检测   typeof:基础类型正常检测(除了null),引用类型返回'object'(除了function)。   instanceof:基础类型不能检测,引用类型可以正常检测。   Object.prototype.toString.call():基础和引用类型都能检查,返回"[objectXxxx]" JS浅拷贝   object.assign(target,...sources);   letcloneObj={...obj};   [].concat();   arr.slice();   assign和扩展运算缺点:1、不能拷贝对象的继承属性。2、不能拷贝对象的不可枚举属性。3、可以拷贝Symbol属性。 JS深拷贝   1、SON.parse(JSON.stringify(x))   缺点:函数、undefin

  • adb工具包使用方法

    adb工具包使用方法 1手机连接电脑,在充电模式下进行。2在电脑上解压adb.zip后,把adb文件放在电脑C盘根目录下。3点击电脑开始>运行>输入cmd>再就进cmd4输入cdc:\adb5再输入adbshell6然后再输入cddata/system7输入ls,查看data/system里面的文件8输入rmpassword.key9输入reboot或手动重启手机生效。(其实,不用重启也可以的了,直接解锁,密码怎么滑都对,最好还是重启一下呗.)

  • 分治递归

    分治 分治,就是分而治之,也就是将大问题分成多个子问题,再由子问题分成更小子问题,直到可以直接得到答案,再由当前的问题返回给上一个子问题     归并排序 归并排序思路:先分再合,每次找到数组的中间值,然后分成左右两遍排序,不断分成两部分,直到每部分只剩下一个数,然后将数组合并成有序的     P1908逆序对 题目描述 猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。 最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a_i>a_jai​>aj​ 且 i<ji<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。 Update:数据已加强。 输入格式 第一行,一个数 nn,表示序列中有 nn个数。 第二行 nn 个数,表示给定的序列。

相关推荐

推荐阅读