19. ClustrixDB 执行计划解读
EXPLAIN语句用于显示ClustrixDB查询优化器(也称为Sierra)如何执行INSERT、SELECT、UPDATE和DELETE语句。EXPLAIN的输出有三列:
- Operation - 完成一项任务的内部操作员
- Est. Cost - 估计成本是与执行操作所需的挂钟时间成比例的度量
- Est. Rows - Sierra认为操作员将输出的估计行数
它们将执行计划描述为实现声明性SQL语句的物理计划。在大多数情况下,EXPLAIN的输出中的每一行都代表一个单独的操作,该操作收集输入,或者处理在接下来的行中缩进一级的输入。换句话说,大多数explain语句可以在缩进程度最高的语句首先执行,而整个执行过程则是缩进程度最低的语句。
它们将执行计划描述为实现声明性SQL语句的物理计划。在大多数情况下,EXPLAIN的输出中的每一行都代表一个单独的操作,该操作收集输入,或者处理在接下来的行中缩进一级的输入。换句话说,大多数explain语句可以在缩进程度最高的语句首先执行,而整个执行过程则是缩进程度最低的语句。
创建数据
为了演示EXPLAIN输出,我们将通过一个定义和使用数据库来跟踪客户和销售给他们的产品的练习。此示例仅用于说明,并不一定是为应用程序设计数据的好方法——应用程序的良好数据模型将取决于您的业务需求和使用模式。这个数据模型关注的是关系,而不是完整的数据一致性模型。
我们将从这个基本数据模型开始。(下载这里使用的脚本。)
sql> CREATE TABLE customers (
c_id INTEGER AUTO_INCREMENT
, name VARCHAR()
, address VARCHAR()
, city VARCHAR()
, state CHAR()
, zip CHAR()
, PRIMARY KEY c_pk (c_id)
) /*$ SLICES=3 */;
sql> CREATE TABLE products (
p_id INTEGER AUTO_INCREMENT
, name VARCHAR()
, price DECIMAL(,)
, PRIMARY KEY p_pk (p_id)
) /*$ SLICES=3 */;
sql> CREATE TABLE orders (
o_id INTEGER AUTO_INCREMENT
, c_id INTEGER
, created_on DATETIME
, PRIMARY KEY o_pk (o_id)
, KEY c_fk (c_id)
, CONSTRAINT FOREIGN KEY c_fk (c_id) REFERENCES customers (c_id)
) /*$ SLICES=3 */;
sql> CREATE TABLE order_items (
oi_id INTEGER AUTO_INCREMENT
, o_id INTEGER
, p_id INTEGER
, PRIMARY KEY oi_pk (oi_id)
, KEY o_fk (o_id)
, KEY p_fk (p_id)
, CONSTRAINT FOREIGN KEY order_fk (o_id) REFERENCES orders (o_id)
, CONSTRAINT FOREIGN KEY product_fk (p_id) REFERENCES products (p_id)
) /*$ SLICES=3 */;
After populating the database, there are 1,000 customers, 100 products, 4,000 orders and around 10,000 order_items.
查看执行计划
让我们从一个简单的查询开始,它为我们提供了关于所有客户的所有信息。
sql> EXPLAIN SELECT * FROM customers;
+------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+------------------------------------------------------+-----------+-----------+
| stream_combine | 712.70 | 1000.00 |
| index_scan := customers.__idx_customers__PRIMARY | 203.90 | 333.33 |
+------------------------------------------------------+-----------+-----------+
通常,可以先从最里面的缩进读取explain输出,然后按照自己的方式向下缩进,最后在输出的第一行结束。读取上面的解释输出后,首先发生的事情是对索引客户执行index_scan操作。主键索引,并将名称“1”分配给读取的结果。在本例中,不再使用该名称。请注意,尽管关系中有1,000个客户,但估计行数大约是333。这是因为每个index_scan读取的是分布在集群中的数据子集,我们称之为片。在这个关系的模式中,有三个片,所以三个index_scan操作并行运行,以收集客户信息。三个index_scan操作的输出被传递给stream_combine操作符,顾名思义,该操作符将把流组合成一个流,以便将其传递给客户机。stream_combine操作符的工作方式是简单地将第一个输入的全部内容复制到单个流输出中,然后继续操作,直到所有流都被合并。
让我们看看如果向查询添加限制会发生什么。
sql> EXPLAIN SELECT * FROM customers LIMIT ;
+----------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+----------------------------------------------------------+-----------+-----------+
| row_limit LIMIT := param() | 615.70 | 10.00 |
| stream_combine | 615.70 | 30.00 |
| row_limit LIMIT := param() | 203.90 | 10.00 |
| index_scan := customers.__idx_customers__PRIMARY | 203.90 | 333.33 |
+----------------------------------------------------------+-----------+-----------+
这里的执行计划与前面添加row_limit操作符时的执行计划基本相同。row_limit操作符接收输入流,并在满足限制(和偏移量)后关闭输入流。由于有三个并行流,Sierra将row_limit操作符的副本“下推”到每个index_scan流,因为不需要从每个片读取超过10行。合并流之后,我们再次限制输出,以便客户机获得请求的10行。
假设我们想对结果进行排序。
sql> EXPLAIN SELECT * FROM customers ORDER BY c_id;
+------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+------------------------------------------------------+-----------+-----------+
| stream_merge KEYS=[( . "c_id") ASC] | 816.70 | 1000.00 |
| index_scan := customers.__idx_customers__PRIMARY | 203.90 | 333.33 |
+------------------------------------------------------+-----------+-----------+
这个计划类似于未排序的版本,除了这次有一个stream_merge来合并结果,而不是stream_combine。stream_merge操作符的工作方式是根据所提供的顺序将所有传入流中的下一行拉入输出流。在本例中,c_id列的顺序是升序的,因此stream_merge将弹出所有流中比较最小的行。
在ClustrixDB集群中,数据通常是散列分布在各个节点上的,由于stream_combine返回最先到达的数据,因此结果可能与没有分布的数据库不同,并且总是按顺序读取数据。例如:
sql> SELECT * FROM customers LIMIT ;
+------+---------------------+--------------+-------------+-------+-------+
| c_id | name | address | city | state | zip |
+------+---------------------+--------------+-------------+-------+-------+
| | Chanda Nordahl | Maple | Greenville | WA | |
| | Dorinda Tomaselli | Oak | Centerville | OR | |
| | Minerva Donnell | 1st St. | Springfield | WA | |
| | Chanda Nordahl | 1st St. | Fairview | OR | |
| | Dorinda Hougland | Pine | Springfield | OR | |
| | Zackary Velasco | Oak | Springfield | OR | |
| | Tennie Soden | Maple | Centerville | OR | |
| | Shawnee Soden | Maple | Ashland | WA | |
| | Riley Soden | 1st St. | Greenville | WA | |
| | Kathaleen Tomaselli | Maple | Centerville | OR | |
+------+---------------------+--------------+-------------+-------+-------+
重复这个查询可能会得到不同的结果。通过在语句中添加ORDER By子句,我们可以确保得到一致的结果。为了使事情更有趣,我们还将更改从升序到降序的顺序。
sql> EXPLAIN SELECT * FROM customers ORDER BY c_id DESC LIMIT ;
+------------------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+------------------------------------------------------------------+-----------+-----------+
| row_limit LIMIT := param() | 622.70 | 10.00 |
| stream_merge KEYS=[( . "c_id") DSC] | 622.70 | 30.00 |
| row_limit LIMIT := param() | 203.90 | 10.00 |
| index_scan := customers.__idx_customers__PRIMARY REVERSE | 203.90 | 333.33 |
+------------------------------------------------------------------+-----------+-----------+
我们可以看到从这个执行计划,并行数据库将第一次读到主索引和在所有可用的反向片index_scan运营商停止阅读10行发现使用row_limit操作符后,合并这些溪流通过选择最大价值从每个流c_id stream_merge算子,最后限制,通过重复应用10行row_limit算子。
JOIN执行计划
到目前为止,我们一直在研究单个关系读取。Sierra的工作之一是比较不同连接顺序的成本,并选择成本最低的计划。此查询将为order_items中的每一行生成订单id、产品名称和价格。
. | sql> EXPLAIN SELECT o_id, name, price FROM orders o NATURAL JOIN order_items NATURAL JOIN products;
. +-------------------------------------------------------------------------------+-----------+-----------+
. | Operation | Est. Cost | Est. Rows |
. +-------------------------------------------------------------------------------+-----------+-----------+
. | nljoin | 95339.90 | 9882.00 |
. | nljoin | 50870.90 | 9882.00 |
. | stream_combine | 82.70 | 100.00 |
. | index_scan := products.__idx_products__PRIMARY | 23.90 | 33.33 |
. | nljoin | 507.88 | 98.82 |
. | index_scan := order_items.p_fk, p_id = .p_id | 63.19 | 98.82 |
. | index_scan := order_items.__idx_order_items__PRIMARY, oi_id = .oi_id | 4.50 | 1.00 |
. | index_scan := orders.__idx_orders__PRIMARY, o_id = .o_id | 4.50 | 1.00 |
. +-------------------------------------------------------------------------------+-----------+-----------+
这个计划稍微复杂一点,需要更多一点的解释来看看发生了什么。
- 给定缩进,我们可以推断index_scan将首先发生。在解释的输出,我们可以看到p_id index_scan中发现产品的主键的8号线时使用阅读p_fk指数order_items oi_id是10号线时使用阅读第11行order_items主键索引。实际上,产品是通过stream_combine操作符收集的,而order_items信息是通过order_items的nljoin收集的。p_fk和order_items主键索引。
- nljoin操作符是一个嵌套循环连接,它实现了关系等连接。
- 产品stream_combine和order_items nljoin的输出然后在另一个nljoin中联接。
- order_items。o_id用于读取订单,所有结果都放在最后的nljoin中。
查看最终nljoin中的估计行让我们知道,在这个特定的数据集中,Sierra认为大约有9882个order_items行。
Stage | Operation | Lookup/Scan representation | Lookup/Scan Key | Run on Node |
---|---|---|---|---|
1 | Lookup and Forward | __idx_products__PRIMARY | none (all nodes with slices) | The node where the query begins |
2.1 | Index Scan | __idx_products__PRIMARY | None, all rows | Nodes with slices of __idx_products__PRIMARY |
2.2 | Lookup and Forward | p_fk | p_id = 3.p_id | same |
3.1 | Index Scan | p_fk | p_id = 3.p_id | Nodes with slices of p_fk |
3.2 | Join | same | ||
3.3 | Lookup and Forward | __idx_order_items__PRIMARY | oi_id = 2.oi_id | same |
4.1 | Index Scan | __idx_order_items__PRIMARY | oi_id = 2.oi_id | Nodes with slices of __idx_order_items__PRIMARY |
4.2 | Join | same | ||
4.3 | Lookup and Forward | __idx_orders__PRIMARY | o_id = 2.o_id | same |
5.1 | Index Scan | __idx_orders__PRIMARY | o_id = 2.o_id | Nodes with slices of __idx_orders__PRIMARY |
5.2 | Join | |||
5.3 | Lookup and Forward | GTM | none - single GTM node | |
6 | Return to user | The node where the query began |
锁
ClustrixDB使用两阶段锁定(2PL)作为并发控制来保证可串行化。在事务中,Sierra将为写操作和更新操作计划锁。首先,我们将研究一个简单的更新,它在大于10时将price的值增加1。
sql> EXPLAIN UPDATE products SET price = price + WHERE price > ;
+-------------------------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+-------------------------------------------------------------------------+-----------+-----------+
| table_update products | 1211.58 | 81.00 |
| compute expr0 := (.price + param()) | 1211.58 | 81.00 |
| filter (.price > param()) | 1210.50 | 81.00 |
| nljoin | 1208.70 | 90.00 |
| pk_lock "products" exclusive | 803.70 | 90.00 |
| stream_combine | 83.70 | 90.00 |
| filter (.price > param()) | 24.57 | 30.00 |
| index_scan := products.__idx_products__PRIMARY | 23.90 | 33.33 |
| index_scan := products.__idx_products__PRIMARY, p_id = .p_id | 4.50 | 1.00 |
+-------------------------------------------------------------------------+-----------+-----------+
在此查询计划中:
- 我们使用index_scan读取产品主键索引,并将输出发送到“下推”过滤器,该过滤器丢弃每片价格不大于10的行。
- 然后,将这些输出与stream_combine组合在一起,并将该流分布在整个集群中,使用pk_lock操作符对找到的行获取独占主键锁。
- 然后,我们可以使用找到的p_id并使用另一个index_scan读取主键索引。
- 由于在第一个index_scan中找到的行可能在读取该行并获取锁后发生了价格变化,所以将再次应用过滤器。
- 匹配的行被发送给计算price的新值的计算操作符,而新行被发送给写入新值的table_update操作符。
在某些情况下,为每一行修改单独的行锁比简单地获取单个表锁并修改所有符合条件的行要昂贵得多。Sierra优化器将考虑在计划探索期间使用表锁而不是行锁,并选择成本最低的计划。在本例中,100行通常太小,不需要使用表锁,但是如果Sierra选择使用表锁,则计划如下所示。
sql> EXPLAIN UPDATE products SET price = price + ;
+------------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+------------------------------------------------------------+-----------+-----------+
| table_locks | 8084.03 | 100.00 |
| table_update products | 84.03 | 100.00 |
| stream_combine | 84.03 | 100.00 |
| compute expr0 := (.price + param()) | 24.34 | 33.33 |
| table_lock "products" exclusive | 23.90 | 33.33 |
| index_scan := products.__idx_products__PRIMARY | 23.90 | 33.33 |
+------------------------------------------------------------+-----------+-----------+
有趣的是,index_scan看起来像是table_lock的输入。情况并非如此,因为表锁将在读取之前获得。有了这一点,我们可以看到计划:
- 用index_scan读取关系中的所有行。
- 使用compute将价格增加1。
- 使用stream_combine将这些结果合并到单个流中。
- 将输出发送到table_update以写入新值。
table_lock操作符是Sierra的一个辅助操作符,它具有一种启发式的方法,可以在其他更新被阻塞的情况下,平衡相对便宜的单锁,从而在该事务期间消耗壁钟时间。
使用索引来提高性能
到目前为止,我们只检查了读取主键索引来获得结果。我们可以通过添加对给定工作负载有意义的索引来改变这一点。例如,假设我们有一个业务流程,如果我们将客户信息按邮政编码排序并合并成小块,那么该业务流程的工作效果会更好。要获得这些信息:
sql> EXPLAIN SELECT name, address, city, state, zip FROM customers ORDER BY zip LIMIT OFFSET ;
+----------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+----------------------------------------------------------+-----------+-----------+
| row_limit LIMIT := param() | 2632.70 | 10.00 |
| sigma_sort KEYS=[( . "zip") ASC] | 2632.70 | 1000.00 |
| stream_combine | 712.70 | 1000.00 |
| index_scan := customers.__idx_customers__PRIMARY | 203.90 | 333.33 |
+----------------------------------------------------------+-----------+-----------+
它读取主键索引,组合结果,然后将这些行发送给sigma_sort操作符。sigma_sort操作符根据需要在内存或存储中构建一个临时容器,以便对邮政编码找到的行进行排序。一旦所有的结果都被排序,它们就会被传递给row_limit操作符来执行限制和偏移量。
如果我们按顺序读取邮政编码,而不是读取所有的行,对邮政编码进行排序,然后返回下一批10行,那么我们可以显著提高这里的性能。为此,我们在customers.zip上添加一个索引,并查看Sierra如何更改执行计划。
sql> ALTER TABLE customers ADD INDEX (zip);
sql> EXPLAIN SELECT name, address, city, state, zip FROM customers ORDER BY zip LIMIT OFFSET ;
+---------------------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+---------------------------------------------------------------------+-----------+-----------+
| msjoin KEYS=[( . "zip") ASC] | 674.70 | 10.00 |
| row_limit LIMIT := param() | 622.70 | 10.00 |
| stream_merge KEYS=[( . "zip") ASC] | 622.70 | 30.00 |
| row_limit LIMIT := param() | 203.90 | 10.00 |
| index_scan := customers.zip | 203.90 | 333.33 |
| index_scan := customers.__idx_customers__PRIMARY, c_id = .c_id | 4.50 | 1.00 |
+---------------------------------------------------------------------+-----------+-----------+
这里,查询优化器选择:
- 使用index_scan操作符并行读取所有片上的custom.zip索引。
- 使用“下推”row_limit操作符限制结果。
- 合并这些结果并使用stream_merge操作符保持顺序。
- 使用另一个row_limit限制合并的结果。
- 使用zip索引中找到的c_id来读取该行的其余部分。
- 使用msjoin操作符执行等连接。
msjoin操作符是一个“合并排序嵌套循环连接”,它类似于nljoin,但在连接期间保留排序顺序。请注意,在此计划中,zip索引的排序顺序将被读取,并在整个计划中始终保持不变,从而消除了创建sigma容器来对结果进行排序的需要。换句话说,这个计划在执行过程中将所有结果流化,这在读取数百万行数据时是一个重要的考虑因素。
聚合
在使用关系数据库时,另一个常见的任务是筛选大数据来计算总和、平均值、最小值或最大值。这些查询是通过向语句中添加一个GROUP by子句来执行的,该子句声明希望如何聚合数据。ClustrixDB还实现了对GROUP BY的MySQL扩展,允许在输出列中包含非聚合列。如果GROUP BY columns和非聚合列之间没有一对一的关系,那么非聚合列的值将是该行中的一个值,不过没有定义返回哪个值。因为我们的数据中有zip和state之间的一对一映射,所以我们可以生成一个结果集来为我们生成映射。
sql> EXPLAIN SELECT zip, state FROM customers GROUP BY zip;
+--------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+--------------------------------------------------------+-----------+-----------+
| sigma_distinct_combine KEYS=(( . "zip")) | 1303.90 | 1000.00 |
| sigma_distinct_partial KEYS=(( . "zip")) | 203.90 | 1000.00 |
| index_scan := customers.__idx_customers__PRIMARY | 203.90 | 333.33 |
+--------------------------------------------------------+-----------+-----------+
这个查询将:
- 首先,执行index_scan并将输出发送给sigma_distinct_partial操作符。
- sigma_distinct_partial操作符,它在读取同一节点上的键的不同值上产生一行输出。
- 然后将这些不同的值发送给sigma_distinct_combine操作符,该操作符将对发起查询的节点上的键执行相同的不同操作。
对于更实际的聚合,让我们假设我们想要查找每个客户下了多少订单以及该客户的姓名。
sql> EXPLAIN SELECT c.name, COUNT(*) FROM orders o NATURAL JOIN customers c GROUP BY o.c_id;
+-------------------------------------------------------------------------------+-----------+-----------+
| Operation | Est. Cost | Est. Rows |
+-------------------------------------------------------------------------------+-----------+-----------+
| hash_aggregate_combine GROUPBY(( . "c_id")) expr1 := countsum(( . "expr1")) | 12780.38 | 4056.80 |
| hash_aggregate_partial GROUPBY(( . "c_id")) expr1 := count(( . "expr0")) | 7100.87 | 4056.80 |
| compute expr0 := param() | 7100.87 | 4056.80 |
| nljoin | 7046.78 | 4056.80 |
| stream_combine | 712.70 | 1000.00 |
| index_scan := customers.__idx_customers__PRIMARY | 203.90 | 333.33 |
| index_scan := orders.c_fk, c_id = .c_id | 6.33 | 4.06 |
+-------------------------------------------------------------------------------+-----------+-----------+
在这个计划:
- 首先是客户主键的index_scan并与stream_combine组合,然后使用c_id读取订单。带有另一个index_scan的c_fk索引。
- 这些结果在我们读取订单的节点上连接。使用nljoin操作符建立c_fk索引,并使用同一个节点上的hash_aggregate_partial操作符进行分组和计数。
- 然后,将结果发送到原始节点上的hash_aggregate_combine操作符,以获得最终的组和计数,然后再将行返回给用户。
总结
希望这是对ClustrixDB Sierra查询优化器的解释输出的充分介绍,您可以使用该优化器检查您自己的查询。有关可能出现在EXPLAIN中的操作符的完整列表,请参考Planner操作符列表。有关Sierra如何进行查询优化的更多信息,请参见分布式数据库架构中的查询优化器。
19. ClustrixDB 执行计划解读的更多相关文章
- MySql 执行计划解读
说明 解读执行计划l对于我们日常工作中慢sql的分析和调优有很大帮助,同时在解读的过程中也能知道如何规避慢sql 建议需要了解join匹配原理的知识:https://www.cnblogs.com/L ...
- MySQL执行计划解读
Explain语法 EXPLAIN SELECT …… 变体: 1. EXPLAIN EXTENDED SELECT …… 将执行计划“反编译”成SELECT语句,运行SHOW WARNINGS 可得 ...
- SQL执行计划解读
声明 5.6中desc看不到show warnings,也看不到filtered列 5.7的desc等于5.6的desc extended,这样可以看show warnings,5.6中filtere ...
- [MySQL] explain执行计划解读
Explain语法 EXPLAIN SELECT …… 变体: 1. EXPLAIN EXTENDED SELECT …… 将执行计划“反编译”成SELECT语句,运行SHOW WARNINGS 可得 ...
- 执行计划解读 简朝阳 (Sky Jian) and 那蓝蓝海
http://greemranqq.iteye.com/blog/2072878 http://www.mysqlab.net/ http://www.mysqlpub.com/ http://blo ...
- Postgresql_根据执行计划优化SQL
执行计划路径选择 postgresql查询规划过程中,查询请求的不同执行方案是通过建立不同的路径来表达的,在生成许多符合条件的路径之后,要从中选择出代价最小的路径,把它转化为一个计划,传递给执行器执行 ...
- Execution Plan 执行计划介绍
后面的练习中需要下载 Demo 数据库, 有很多不同的版本, 可以根据个人需要下载. 下载地址 -http://msftdbprodsamples.codeplex.com/ 1. 什么是执行计划 ...
- (转)mysql执行计划分析
转自:https://www.cnblogs.com/liu-ke/p/4432774.html MySQL执行计划解读 Explain语法 EXPLAIN SELECT …… 变体: 1. EX ...
- PostgreSQL 执行计划
简介 PostgreSQL是“世界上最先进的开源关系型数据库”.因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源. MySQL是“世界上最流行的开源关系型数据库”.当 ...
随机推荐
- codeblocks无法识别的16位程序解决方法
被codeblocks心态搞崩了,分享一下经验给大家,具体就是无法运行编译好的程序,还有就是调试功能没法用. 查了很多资料,自己搞了一个终极解决方法:1卸载codeblocks,2打开我的电脑,全盘搜 ...
- JVM 堆内存设置原理(转)
堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...
- python中判断字典中是否存在某个键
python3 中采用 in 方法 #判断字典中某个键是否存在 arr = {"int":"整数","float":"浮点&quo ...
- 使用python 实现 微信好友 个性签名 并 制作 词云图
环境搭建: pip install itchat numpy wordcloud matplotlib jieba 先把上面的几个包安装完成,直接上代码 import itchat from itch ...
- 查看CPU位数的方法
查看电脑cpu的位数 WINDOWS下查看的 方法: 方法一. 在开始→运行 ...
- [转载]排序:长度为n的数组乱序存放着0至n-1. 现在只能进行0与其他数的swap
长度为n的数组乱序存放着0至n-1. 现在只能进行0与其他数的swap 请设计并实现排序. google笔试小题.题目来源:http://wenku.baidu.com/view/5aa818dda5 ...
- phpstorm 快捷键2
1.跨平台. 2.对PHP支持refactor功能.支持断点调试,支持 Symfony2 和 Yii 的 MVC 视图 3.自动生成phpdoc的注释,非常方便进行大型编程. 4.内置支持Zencod ...
- linux新建用户并分配sudo权限
新建用户 useradd [username] 给用户设置密码 passwd [username] 设置sudo权限 首先将sudoers权限设置可写入 chmod u+w /etc/sudoers ...
- RubyGems 库发现了后门版本的网站开发工具 bootstrap-sass
安全研究人员在官方的 RubyGems 库发现了后门版本的网站开发工具 bootstrap-sass.该工具的下载量高达 2800 万次,但这并不意味着下载的所有版本都存在后门,受影响的版本是 v3. ...
- vim 添加显示和行号
方法一: 1.显示当前行行号,在VI的命令模式下输入 :nu 2.显示所有行号,在VI的命令模式下输入 :set nu :set nonu 关闭 方法二: 使用vi编辑~/.vimrc文件,在该文件中 ...