ARTS第六周

ARTS第六周

ARTS是什么?

Algorithm:每周至少做一个leetcode的算法题;
Review:阅读并点评至少一篇英文技术文章;
Tip/Techni:学习至少一个技术技巧;
Share:分享一篇有观点和思考的技术文章。

Algorithm

题目:141. Linked List Cycle

解题思路

本题是链表相关的一道题,题目给出一个链表,问我们这个链表中是否有环。题目中给出了三个例子来帮助我们分析是否有环,我们可以简单理解为,判断链表中是否有节点被超过两个节点的next指针指向,如果有就存在环,如果没有就无环。
?

第一种方法
我们一次遍历链表中的各个节点,并把遍历过节点存到一个set中,每次遍历下一个节点的时候先判断是否它是否存在于set中,如果存在则表示此链表存在环,如果遍历完整个链表都没有发现重复节点,则表示此链表不存在环。
?

第二种方法
设置两个快慢指针,一个指向第一个节点取名为slow,另一个指向第二个节点,取名为fast,slow指针每次走一步,即slow = slow.next,fast指针每次走两步,即fast = fast.next.next,遍历过程中判断slow和fast的值是否相等,如果有环,fast指针会追上slow指针,如果没环,最终fast或fast.next的值会是null。

代码

第一种方法

public boolean hasCycle(ListNode head) {
       Set<ListNode> nodes = new HashSet<>();
        if (head == null || head.next == null){
            return false;
        }

        while (head.next != null){
            if (nodes.contains(head)) {
                return true;
            }else{
                nodes.add(head);
                head = head.next;
            }
        }

        return false;
    }

?

第二种方法

public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null){
            return false;
        }

        ListNode quick = head.next;
        ListNode slow = head;
        while (quick != slow){
            if (quick == null || quick.next == null){
                return false;
            }
            quick = quick.next.next;
            slow = slow.next;
        }

        return true;
    }

Review

这周在medium上看了一篇文章How to work optimally with relational databases,意思就是“如何更好地使用关系型数据库”。下面我就对这篇文章中的主要内容进行翻译(如有错误,敬请指正),完整原文请点击前文链接前往阅读。

理解关系型数据库

存储

MySQL是一种关系型数据库,它的数据是由一行行tuple表示,所有tuple组成一个relation,每行tuple用它的属性来表示。关系如下图所示:

假设我们有一个借书的应用,我们需要存储所有图书的借阅记录,为了存储它们,我们用下面的sql语句创建一个简单的关系表:

> CREATE TABLE book_transactions ( id INTEGER NOT NULL   AUTO_INCREMENT, book_id INTEGER, borrower_id INTEGER, lender_id INTEGER, return_date DATE, PRIMARY KEY (id));

?
这个表看起来就像这样:

book_transactions
------------------------------------------------
id  borrower_id  lender_id  book_id  return_date

?
这里的** id **是主键, borrower_id, lender_id, book_id都是外键,在我们使用我们的应用之后会生成几条记录:

book_transactions
------------------------------------------------
id  borrower_id  lender_id  book_id  return_date
------------------------------------------------
1   1            1          1        2018-01-13
2   2            3          2        2018-01-13
3   1            2          1        2018-01-13

获取数据

我们的应用有一个dashboard页允许用户去查看他们借阅图书的记录,那么让我们来获取某个用户的借阅记录:

> SELECT * FROM book_transactions WHERE borrower_id = 1;
book_transactions
------------------------------------------------
id  borrower_id  lender_id  book_id  return_date
------------------------------------------------
1   1            1          1        2018-01-13
2   1            2          1        2018-01-13

?
这会顺序扫描关系表并把这个用户的数据返回给我们。这看起来很快,因为我们的关系表数据很少。为了能看到精确的查询时间,我们通过下面的命令把** set profiling **设置成true:

> set profiling=1;

?
一旦设置了profiling参数,再此运行查询语句的时候,使用下面的命令就能看到执行时间

> show profiles;

?
这将返回我们执行的查询命所花费的时间。

Query_ID | Duration   | Query
       1 | 0.00254000 | SELECT * FROM book_transactions ...       

?
执行速度似乎很快。

慢慢地,book_transactions表数据越来越多,因为会一直产生很多的记录。

问题

这在我们的关系表中增加了tuples的数量,接着,用户获取借阅记录的时间开始变得更长。MySQL需要遍历所有的tuples去找到结果。

为了插入很多数据到这个表,我写了下面的存储过程:

DELIMITER //
 CREATE PROCEDURE InsertALot()
   BEGIN
   DECLARE i INT DEFAULT 1;
   WHILE (i <= 100000) DO
    INSERT INTO book_transactions (borrower_id, lender_id, book_id,   return_date) VALUES ((FLOOR(1 + RAND() * 60)), (FLOOR(1 + RAND() * 60)), (FLOOR(1 + RAND() * 60)), CURDATE());
    SET i = i+1;
   END WHILE;
 END //
 DELIMITER ;
* It took around 7 minutes to insert 1.5 million data

?
这个存储过程插入了100000条随机数据到我们的book_transactions表,在运行完这个存储过程以后,分析器显示运行时间有略微增长:

Query_ID | Duration   | Query
       1 | 0.07151000 | SELECT * FROM book_transactions ...

?
让我们多加一点数据,运行上面的存储过程,然后看看会发生什么。随着加入越来越多的数据,查询时长也在随之增加。当有1.5百万数据插入到这张表的时候,查询的响应时间已经明显加长了。

Query_ID | Duration   | Query
       1 | 0.36795200 | SELECT * FROM book_transactions ...

?
这只是一个简单的涉及整数字段的查询。

随着更多的复合查询,排序查询和计数查询,执行时间将变得更长。

对于单个查询,这似乎不是一个很长的时间,但当我们每分钟运行数千甚至上百万的查询的时候,就会有很大的不同。

将有更长的等待时间,这会降低整个应用的性能。同样的一个查询,执行时间会从2ms增加到370ms。

加速

索引

MySQL和其他数据库提供了索引,一种有助于更快获取数据的数据结构。

在MySQL中,有不同类型的索引:

  • 主键索引 -- 加在主键上的索引。默认情况下,主键都是加了索引的。它确保任意两行数据不会出现同一个主键值。
  • 唯一索引 -- 唯一索引可以确保没有两行数据会有同一个值,可以使用唯一索引存储多个NULL值。
  • 普通索引 -- 可以加在除主键以外的任何字段上。
  • 全文索引 -- 全文索引可以帮助针对字符串数据的查询

索引存储的方式主要有两种:

Hash -- 这种方式主要用于精确匹配,不适用于比较。

B-Tree -- 这是存储上面提到的索引类型最常用的方式。

MySQL适用B-Tree作为它的默认索引格式,数据存储在一颗二叉树中,这样能快速检索数据。

用B-Tree组织数据有助于跳过关系表中所有tuples的全表扫描。

上面图中的B-Tree一共有16个节点,假设我们需要找到数字6,我们总共只需要遍历3次就能找到这个数,这有助于提高搜索性能。

所以要提高book_transactions表的性能,让我们在lender_id字段上添加索引。

> CREATE INDEX lenderId ON book_transactions(lender_id)
----------------------------------------------------
* It took around 6.18sec Adding this index

?
上面的命令在lender_id字段上添加了一个索引。重新运行之前的查询语句,让我们来看看它如何影响我们拥有的1.5百万数据的性能。

> SELECT * FROM book_transactions WHERE lender_id = 1;
Query_ID | Duration   | Query
       1 | 0.00787600 | SELECT * FROM book_transactions ...

?
哇,又像之前那么快了!

现在和只有3条记录的时候一样快,通过正确的索引添加,我们能看到显著的性能提升。

复合索引和单一索引

刚才我们添加的是单一索引,索引也能加到复合字段上。

如果我们的查询包含多个字段,一个复合索引将帮助我们,我们可以使用下面的命令来添加一个复合索引:

> CREATE INDEX lenderReturnDate ON book_transactions(lender_id, return_date);

索引的其他用法

查询不是索引唯一的用途,它们也能被用在ORDER BY条件上,让我们根据lender_id字段对所有记录排序。

> SELECT * FROM book_transactions ORDER BY lender_id;
1517185 rows in set (4.08 sec)

?
4.08 秒,太长了!出了什么问题吗?我们明明加了索引。让我们借助EXPLAIN语句深入研究看看这个查询语句是怎么被执行的。

使用ExPlain

我们添加一个explain语句来看这个查询在我们当前的数据集中究竟是怎么执行的。

> EXPLAIN SELECT * FROM book_transactions ORDER BY lender_id;

?
输出如下:

explain语句返回了几个字段,让我们看看上图中的表格来找到问题所在。

rows:被扫描的行数
filtered:获取数据时被扫描行所占的百分比
type:如果使用索引则给出,ALL表示没有使用索引
possible_keys, key, key_len 都是NULL,表示没有使用索引

那么为什么这个查询没有用索引?

这是因为我们在查询语句使用了select *,意味着我们会查询表中的所有字段。

索引只有那些加了索引的字段的信息,不包含其他字段,这意味着MySQL需要去主表再次获取数据。

那么我们应该如何写我们的查询语句?

只查需要的字段

要删除转到主表的查询,我们需要只查索引表中存在的值。让我们修改一下查询语句:

> SELECT lender_id FROM book_transactions ORDER BY lender_id;

?
这将在0.46秒内返回结果,哪个更快?但还是有提高的余地。

当这个查询是在1.5百万条记录上完成的,它需要花费更多的时间去加载数据到内存。

使用Limit

我们或许不需要一次性获取1.5百万条数据。使用LIMIT批量获取数据代替获取全量数据是一种更好的方式。

> SELECT lender_id
  FROM book_transactions
  ORDER BY lender_id LIMIT 1000;

?
有了LIMIT,响应时间大大提升,只需要0.0025秒。我们能使用OFFSET获取下一批数据。

> SELECT lender_id
  FROM book_transactions
  ORDER BY lender_id LIMIT 1000 OFFSET 1000;

?
这会获取下一批1000条数据,用这种方式我们可以增加offset和limit获取所有数据。但是有一个陷阱,随着offset的增加,查询性能也会下降。

这是因为MySQL会扫描全表数据去到达offset点,因此最好不要使用很高的offset。

计数查询怎么样?

InnoDB引擎有并发写的能力。这使其具有高扩展性并提高了每秒的吞吐量。

但这是有代价的,InnoDB无法为任意表记录数添加缓存计数器。所以,计数器必须通过遍历所有过滤数据完成实时计数,这使得COUNT查询比较慢。

所以推荐在应用逻辑里为大数据量的数据计算总计数。

为什么不给所有字段都加索引

增加索引能提高性能,但它也有代价。应该有效地使用,给更多的字段增加索引会有下面的问题:

  • 占用大量的内存,需要更大的机器
  • 当我们做删除操作的时候,会重建索引(CPU密集型操作,慢删除)
  • 当我们做插入操作的时候,会重建索引(CPU密集型操作,慢插入)
  • 更新不会做全局重建索引,所以更新操作时更快的,cpu效率更高

我们知道添加索引很有帮助,但我们不能在所有数据上都添加索引除非它能提升性能。

分区

当我们构建索引的时候,我们只有索引字段的信息,没有索引中不存在的字段数据。

那么,就如前文说的,MySQL需要回表获取别的字段数据,这会减慢执行时间。

我们可以使用分区来解决这个问题。

分区是一种技术,MySQL将一张表的数据拆分成多张表,但还是当一张表来管理。

在表中执行任何类型的操作时,我们需要指定正在使用的分区。随着数据被分解,MySQL有一个较小的数据集可供查询,根据需要确定正确的分区是高性能的关键。

但如果我们一直用同一台机器,它能扩展吗?

分片

当面对庞大的数据集,将所有数据存储在同一台机器上可能会很麻烦。

特定分区可能很重,需要更多查询,而其他分区则较少。所以一个会影响另一个。它们无法单独扩展。

假设最近三个月的数据是最常用的,而较旧的数据使用较少。也许最近的数据大多是更新/创建的,而旧的数据大多只是被读过。

要解决此问题,我们可以将最近三个月的分区移动到另一台计算机。

分片是一种将大数据集分成较小块的方式,然后转移到单独的RDBMS。换句话说,分片也可以称为“水平分区”。

关系数据库能够随着应用程序的增长而扩展。找出正确的索引并根据需要调整基础架构是必要的。

Tip/Techni

本周分享一个cpu占用100%问题的解决方法,主要用到两个命令top和perf,top用来确认引发 CPU 性能问题的来源;perf用来排查出引起性能问题的具体函数。

具体示例

使用top命令查看当前cpu使用率,按下数字1,切换到每个cpu的使用率:

$ top
...
%Cpu0  : 98.7 us,  1.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 99.3 us,  0.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
...
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
21514 daemon    20   0  336696  16384   8712 R  41.9  0.2   0:06.00 php-fpm
21513 daemon    20   0  336696  13244   5572 R  40.2  0.2   0:06.08 php-fpm
21515 daemon    20   0  336696  16384   8712 R  40.2  0.2   0:05.67 php-fpm
21512 daemon    20   0  336696  13244   5572 R  39.9  0.2   0:05.87 php-fpm
21516 daemon    20   0  336696  16384   8712 R  35.9  0.2   0:05.61 php-fpm

?

这里有多个使用率在40%左右的进程,我们找其中一个使用 perf 分析下:

# -g 开启调用关系分析,-p 指定 php-fpm 的进程号 21515
$ perf top -g -p 21515

?
按方向键切换到 php-fpm,再按下回车键展开 php-fpm 的调用关系,调用关系最终到了 sqrt 和 add_function。

然后我们需要去源码中寻找这两个方法的踪迹,问题就藏在使用到这两个方法的地方。

Share

今天分享一则轻松一点的讯息Mozilla 抱怨谷歌偏心! 火狐/Edge 加载 YouTube 缓慢,看到这则新闻的时候我瞬间联想到了最近美国和中国的贸易战,两者何其相似,都是强者无理欺负弱者而弱者只能抱怨的案例,自古以来亘古不变的真理就是强者才有话语权,实力说话,不然就要“挨打”,不仅仅是商场、外交是这样,生活中处处都是这个道理的鲜活例子,作为个人也要努力变强,这样才有话语权,才能有更多的自由做你想做的事,过你想过的生活。

原文地址:https://www.cnblogs.com/muxuanchan/p/10163948.html

时间: 2024-10-03 07:45:21

ARTS第六周的相关文章

20145317《信息安全系统设计基础》第六周学习总结(1)

20145317<信息安全系统设计基础>第六周学习总结(1) 第四章 处理器体系结构 指令体系结构:一个处理器支持的指令和指令的字节级编码 4.1Y86指令集体系结构 Y86:包括定义各种状态元素.指令集和它们的编码.一组编程规范和异常事件处理. Y86程序中的每条指令都会读取或修改处理器状态的某些部分.Y86具体包括:8个程序寄存器.3个条件码ZF\SF\OF.程序计数器(PC) Y86用虚拟地址引用存储器位置. 程序状态的最后一个部分是状态码Stat,它表明程序执行的总体状态. 注意:条件

20145222《信息安全系统设计基础》第六周学习总结(1)

20145222<信息安全系统设计基础>第六周学习总结(1) 第四章 处理器体系结构 指令体系结构:一个处理器支持的指令和指令的字节级编码 4.1Y86指令集体系结构 · Y86:包括定义各种状态元素.指令集和它们的编码.一组编程规范和异常事件处理. · Y86程序中的每条指令都会读取或修改处理器状态的某些部分.· Y86具体包括:8个程序寄存器.3个条件码ZF\SF\OF.程序计数器(PC) · Y86用虚拟地址引用存储器位置. 程序状态的最后一个部分是状态码Stat,它表明程序执行的总体状

第六周总结

个人篇: 第六周的学习主要是OOP最后的几章内容,内容都是相对于之前的基础更加的抽象,在学习的过程中应该更注重理解层次上,然后当然也要实战练习. ~~GUI的内容,讲Jtable和Jtree进行了学习,可以实现ATM机的表格查询和增加用户以及删减用户:制作了QQ聊天列表. ~~I/O流的学习,主要是掌握输入流和输出流的字节流包装成包装流,了解各种流的有点和缺点,在运用中实现写入和只读的,包括文件或者字符串.结合joi包的导入后,可以控制excel的写入和只读, 将excel模拟成数据库,然后用I

java第六周学习总结

学号20145336 <Java程序设计>第五周学习总结 教材学习内容总结 第十章 输入与输出 InputStream与OutputStream java将输入/输出抽象化为串流,数据有来源及目的地,衔接两者的是串流对象.从程序角度来看,如果将数据从来源中取出,可以使用输入串流,如果将数据写入目的地可以使用输出串流.在java中,输入串流对象为java.io.InputStream实例,输出串流对象为java.io.OutputStream实例.流(Stream)是对「输入输出」的抽象,注意「

20145237第六周学习总结

20145237第6周学习总结 教材学习内容总结 第十章 •InputStream与OutputStream 流(Stream)是对「输入输出」的抽象. read:每次会尝试读入byte数组长度的数据,并返回实际读入的字节.为-1时即为未读取到数据. write:指定要写出的byte数组.初始索引与数组长度. •标准输入输出//这部分已经很熟悉了 System.in: 标准输入,默认关联到键盘(终端输入) System.out: 标准输出,默认关联到显示器(终端输出) System.err: 标

第六周周总结

在第六周中是忙碌的,对于高数,上课的内容也在快速的翻篇,我的大脑也在快速的运转着,讲概念的时候我很难理解过来,但是讲习题的时候勉勉强强能听的懂,每次在听不懂的地方我都会打个问号,以便于自己下课后方便问同学,我觉得这也是初中以来养成的一个好习惯吧,数学的作业真的太伤脑细胞了,往往要思考很久才能做出一题,但是做了不管对错也总比看着答案抄一遍答案好的多.自己对数学不会,可是有时候又很喜欢计算的这种题型,就觉得很好玩啊.我希望自己努力一把吧,争取期中的时候能考好点,要开始认真准备期中了. 讲讲专业课吧,

《Linux内核分析》第六周学习笔记

<Linux内核分析>第六周学习笔记 进程的描述和创建 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 [学习视频时间:1小时 撰写博客时间:2小时] [学习内容:进程创建的过程.使用gdb跟踪分析内核处理函数sys_clone] 一.进程的描述 1.1 进程描述符task_struct数据结构(一) 1. 进程控制块PCB——task_struct 为了管理进程,内核

【项目计划&#183;微信】第六周项目计划与分工

第六周项目计划与分工 制定时间:2014年10月26日 地点:逸夫馆研讨间02号 到场人:杨妍喆.林聪.周伯威.徐子茹 [已完成]第五周任务及分工 a.  调研与分析报告; b. 开发平台学习及分析;  c. Vision文档与用户故事修订 子茹和我调研产品,我们找到了四个主流的微信订票平台,我负责调研分析“艾迪票务”和“哈票网”,子茹负责"QQ订票"和“木偶剧场”: 周伯威负责开发平台的学习分析报告: 林聪负责Vision文档与用户故事的修订. 第六周任务及其分工 a. 配置相关的开

第十六周学习进度表

时间 第十六周 所花时间 10个小时左右 代码量 300行左右 博客量 1篇 了解到的知识点 网页版的显示作业