Neo4j数据库访问Neo4j访问的两种方式嵌入式数据库服务器模式(通过REST的访问)它是由应用程序的性质（neo4j是独立服务器 还是和程序在一起),性能，监控和数据安全性来决定架构选择。An embedded database（嵌入式数据库）嵌入式Neo4j数据库是性能的最佳选择。 通过指定数据存储的路径以编程方式访问嵌入式数据库。我们选择嵌入式数据库出于以下原因：使用Java作为我们项目的编程语言时应用程序是独立的程序追求很高的性能Neo4j Server（服务器模式）Neo4j Server是相互操作性，安全性和监控的最佳选择。 实际上，REST接口允许所有现代平台和编程语言与它进行互操作。 此外，作为独立应用程序，它比嵌入式配置更安全（客户端中的潜在故障不会影响服务器），并且更易于监控。 如果我们选择使用这种模式，我们的应用程序将充当Neo4j服务器的客户端。要连接到Neo4j服务器，可以使用任何编程语言的REST访问数据库。Java客户端操作Neo4j嵌入式模式<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j</artifactId> <version>3.5.5</version> </dependency>package com.neodemo; import org.neo4j.graphdb.*; import org.neo4j.graphdb.factory.GraphDatabaseFactory; import java.io.File; import java.util.HashMap; import java.util.Map; public class EmbeddedNeo4jAdd { private static final File databaseDirectory = new File( "target/graph.db" ); public static void main(String[] args) { GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase(databaseDirectory); System.out.println("Database Load!"); Transaction tx = graphDb.beginTx(); Node n1 = graphDb.createNode(); n1.setProperty("name", "张三"); n1.setProperty("character", "A"); n1.setProperty("gender",1); n1.setProperty("money", 1101); n1.addLabel(new Label() { @Override public String name() { return "Person"; } }); String cql = "CREATE (p:Person{name:'李四',character:'B',gender:1,money:21000})"; graphDb.execute(cql); tx.success(); tx.close(); System.out.println("Database Shutdown!"); graphDb.shutdown(); } }package com.neodemo; import org.neo4j.graphdb.*; import org.neo4j.graphdb.factory.GraphDatabaseFactory; import java.io.File; import java.util.HashMap; import java.util.Map; public class EmbeddedNeo4jQueryAll { private static final File databaseDirectory = new File( "target/graph.db" ); public static void main(String[] args) { GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase(databaseDirectory); System.out.println("Database Load!"); String cql = "MATCH (a:Person) where a.money < $money return a"; Map<String, Object> paramerters = new HashMap<String, Object>(); paramerters.put("money", 25000); Transaction tx = graphDb.beginTx(); Result result = graphDb.execute(cql,paramerters); while (result.hasNext()) { Map<String, Object> row = result.next(); for (String key : result.columns()) { Node nd = (Node) row.get(key); System.out.printf("%s = %s:%s%n", key, nd.getProperty("name"),nd.getProperty("money")); } } tx.success(); tx.close(); System.out.println("Database Shutdown!"); graphDb.shutdown(); } }服务器模式<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j-ogm-bolt-driver</artifactId> <version>3.2.10</version> </dependency>package com.neodemo; import org.neo4j.driver.*; import static org.neo4j.driver.Values.parameters; public class Neo4jServerMain { public static void main(String[] args) { Driver driver = GraphDatabase.driver( "bolt://127.0.0.1:7687", AuthTokens.basic( "neo4j", "123456" ) ); Session session = driver.session(); String cql = "MATCH (a:Person) WHERE a.money > $money RETURN a.name AS name, a.money AS money order by a.money "; Result result = session.run( cql, parameters( "money", 1000 ) ); while ( result.hasNext() { Record record = result.next(); System.out.println( record.get( "name" ).asString() + " " + record.get( "money" ).asDouble() ); } session.close(); driver.close(); } }package com.neodemo; import org.neo4j.driver.*; import static org.neo4j.driver.Values.parameters; public class Neo4jServerMain2 { public static void main(String[] args) { Driver driver = GraphDatabase.driver( "bolt://127.0.0.1:7687", AuthTokens.basic( "neo4j", "123456" ) ); Session session = driver.session(); String cql = "MATCH p=shortestPath((person:Person {name:$startName})-[*]-(person2:Person {name:$endName} )) RETURN p"; Result result = session.run( cql,parameters("startName","王启年","endName","九品射手燕小乙") ); while ( result.hasNext() ) { Record record = result.next(); System.out.println(record); } session.close(); driver.close(); } }SpringBoot 整合Neo4j导入jar包<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.neodemo</groupId> <artifactId>neo4j_springboot_demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency> <dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j-ogm-bolt-driver</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>建立实体类package com.neodemo.bean; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; @NodeEntity public class Person { @Id @GeneratedValue private Long id; @Property("cid") // 数据库和实体属性不对应时，用此注解 private int pid; @Property // 数据库和实体属性对应时，可以不用此注解 private String name; private String character; private double money; private int gender; private int age; private String description; @Relationship(type = "Friends",direction = Relationship.INCOMING) private Set<Person> relationPersons; @Override public String toString() { return "Person{" + "id=" + id + ", pid=" + pid + ", name='" + name + '\'' + ", character='" + character + '\'' + ", money=" + money + ", gender=" + gender + ", age=" + age + ", description='" + description + '\'' + ", relationPersons=" + relationPersons + '}'; } public Person() { } public Person(Long id, String name, String character, double money, int gender, int age, String description) { this.id = id; this.name = name; this.character = character; this.money = money; this.gender = gender; this.age = age; this.description = description; } public Set<Person> getRelationPersons() { return relationPersons; } public void setRelationPersons(Set<Person> relationPersons) { this.relationPersons = relationPersons; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCharacter() { return character; } public void setCharacter(String character) { this.character = character; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } public int isGender() { return gender; } public void setGender(int gender) { this.gender = gender; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }数据持久化类package com.neodemo.repository; import com.neodemo.bean.Person; import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.stereotype.Repository; import java.util.*; @Repository public interface PersonRepository extends Neo4jRepository<Person,Long> { @Query("match(p:Person) where p.money > {0} return p") List<Person> personList(double money); @Query("MATCH p=shortestPath((person:Person {name:{0}})-[*1..4]-(person2:Person {name:{1}}) ) RETURN p") List<Person> shortestPath(String startName,String endName); }或者使用@Query("match(p:Person) where p.money>{money} return p") List<Person> personList(@Param("money") double money); // 指定开始的名字 和 结束的名字 查询最短路径 限定深度为4以内包含4 @Query("match p=shortestPath((person:Person{name:{startName}})-[*1..4]-(person2:Person {name:{endName}})) return p") List<Person> shortestPath(@Param("startName") String startName,@Param("endName") String endName);配置文件 application.ymlspring: data: neo4j: username: neo4j password: 123456 uri: bolt://192.168.211.133:7687 #uri: http://192.168.211.133:7474 #uri: file:///target/graph.db编写服务类package com.neodemo.service; import com.neodemo.bean.Person; import com.neodemo.repository.PersonRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; @Service public class Neo4jPersonService { @Autowired private PersonRepository personRepository; public List<Person> personList(){ return personRepository.personList(); } public Person save(Person person){ return personRepository.save(person); } public List<Person> shortestPath(String startName, String endName){ return personRepository.shortestPath(startName,endName); } }编写测试类package com.neodemo; import com.neodemo.bean.Person; import com.neodemo.service.Neo4jPersonService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import java.util.List; @SpringBootApplication public class TestNeo4jBootApp { public static void main(String[] args) { ApplicationContext app = SpringApplication.run(TestNeo4jBootApp.class,args); Neo4jPersonService personService = app.getBean(Neo4jPersonService.class); System.out.println(personService); List<Person> datas = personService.personListAll(); System.out.println(datas); System.out.println(personService. shortestPath("王启年", "九品射手燕小乙")); } }
Neo4j - 数据库备份和恢复在对Neo4j数据进行备份、还原、迁移的操作时，首先要关闭neo4j./bin/neo4j stop数据备份到文件./bin/neo4j-admin dump --database=graph.db --to=/root/qyn.dump还原、迁移之前 ，关闭neo4j服务。操作同上./bin/neo4j-admin load --from=/root/qyn.dump --database=graph.db --force重启服务./bin/neo4j start注意，运行数据备份可能会警告WARNING: Max 1024 open files allowed, minimum of 40000 recommended. See the Neo4j manual编辑这个文件vi /etc/security/limits.conf在文件最后加入下面这段 修改最大打开文件限制soft nofile 65535hard nofile 65535重启服务器再次执行上面的步骤 警告就没有了调优思路增加服务器内存 和 调整neo4j配置文件# java heap 初始值 dbms.memory.heap.initial_size=1g # java heap 最大值，一般不要超过可用物理内存的80％ dbms.memory.heap.max_size=16g # pagecache大小，官方建议设为：(总内存-dbms.memory.heap.max_size)/2， dbms.memory.pagecache.size=2gneo4j刚启动数据是冷的需要预热MATCH (n) OPTIONAL MATCH (n)-[r]->() RETURN count(n.name) + count(r);查看执行计划进行索引优化Cypher查询计划程序将每个查询转换为执行计划。 执行计划告诉Neo4j在执行查询时要执行哪些操作。对执行计划的生成，Neo4j使用的都是基于成本的优化器（Cost Based Optimizer，CBO），用于制订精确的执行过程。可以采用如下两种不同的方式了解其内部的工作机制：EXPLAIN：是解释机制，加入该关键字的Cypher语句可以预览执行的过程但并不实际执行，所以也不会产生任何结果。PROFILE：则是画像机制，查询中使用该关键字，不仅能够看到执行计划的详细内容，也可以看到查询的执行结果。关注指标：estimated rows： 需要被扫描行数的预估值dbhits： 实际运行结果的命中绩效两个值都是越小越好使用索引和不使用索引对比MATCH (p { name : '范闲' }) RETURN p在之前加上profile来进行查询，可以查看查询计划
CQL 函数字符串函数S.No.功能描述1UPPER它用于将所有字母更改为大写字母。2LOWER它用于将所有字母改为小写字母。3SUBSTRING它用于获取给定String的子字符串。4REPLACE它用于替换一个字符串的子字符串。MATCH (p:Person) RETURN ID(p),LOWER(p.character)match(p:Person) return p.character,lower(p.character),p.name,substring(p.name,2),replace(p.name,"子","zi")聚合函数S.No.聚集功能描述1COUNT它返回由MATCH命令返回的行数。2MAX它从MATCH命令返回的一组行返回最大值。3MIN它返回由MATCH命令返回的一组行的最小值。4SUM它返回由MATCH命令返回的所有行的求和值。5AVG它返回由MATCH命令返回的所有行的平均值。MATCH (p:Person) RETURN MAX(p.money),SUM(p.money)关系函数S.No.功能描述1STARTNODE它用于知道关系的开始节点。2ENDNODE它用于知道关系的结束节点。3ID它用于知道关系的ID。4TYPE它用于知道字符串表示中的一个关系的TYPE。match p = (:Person {name:"林婉儿"})-[r:Couple]-(:Person) RETURN STARTNODE(r)shortestPath 函数返回最短的pathMATCH p=shortestPath( (node1)-[*]-(node2) ) RETURN length(p), nodes(p)MATCH p=shortestPath((person:Person {name:"王启年"})-[*]-(person2:Person {name:"九品射手燕小乙"}) ) RETURN length(p), nodes(p)CQL多深度关系节点with关键字查询三层级关系节点如下：with可以将前面查询结果作为后面查询条件match (na:Person)-[re]->(nb:Person) where na.name="范闲" WITH na,re,nb match (nb:Person)-[re2]->(nc:Person) return na,re,nb,re2,nc match (na:Person)-[re]->(nb:Person) where na.name="林婉儿" WITH na,re,nb match (nb:Person)-[re2]->(nc:Person) return na,re,nb,re2,nc match (na:Person)-[re]-(nb:Person) where na.name="林婉儿" WITH na,re,nb match (nb:Person)-[re2]->(nc:Person) return na,re,nb,re2,nc match (na:Person)-[re]-(nb:Person) where na.name="林婉儿" WITH na,re,nb match (nb:Person)-[re2:Friends]->(nc:Person) return na,re,nb,re2,nc直接拼接关系节点查询match (na:Person{name:"范闲"})-[re]->(nb:Person)-[re2]->(nc:Person) return na,re,nb,re2,nc为了方便，可以将查询结果赋给变量，然后返回match data=(na:Person{name:"范闲"})-[re]->(nb:Person)-[re2]->(nc:Person) return data使用深度运算符当实现多深度关系节点查询时，显然使用以上方式比较繁琐。可变数量的关系->节点可以使用 -[:TYPE*minHops..maxHops]-。查询：match data=(na:Person{name:"范闲"})-[*1..2]-(nb:Person) return data事务为了保持数据的完整性和保证良好的事务行为，Neo4j也支持ACID特性 。注意:所有对Neo4j数据库的数据修改操作都必须封装在事务里。默认的isolation level是READ_COMMITTED。死锁保护已经内置到核心事务管理。(Neo4j会在死锁发生之前检测死锁并抛出异常。在异常抛出之前，事务会被标志为回滚。当事务结束时，事务会释放它所持有的锁，则该事务的锁所引起的死锁也就是解除，其他事务就可以继续执行。当用户需要时，抛出异常的事务可以尝试重新执行)除特别说明,Neo4j的API的操作都是线程安全的，Neo4j数据库的操作也就没有必要使用外部的同步方法。索引简介Neo4j CQL支持节点或关系属性上的索引，以提高应用程序的性能。可以为具有相同标签名称的属性上创建索引。可以在MATCH或WHERE等运算符上使用这些索引列来改进CQL的执行。创建单一索引CREATE INDEX ON :Label(property)例如：CREATE INDEX ON :Person(name)创建复合索引CREATE INDEX ON :Person(age, gender)全文模式索引之前的常规模式索引只能对字符串进行精确匹配或者前后缀索引(startswith,endswith,contains)，全文索引将标记化索引字符串值，因此它可以匹配字符串中任何位置的术语。索引字符串如何被标记化并分解为术语，取决于配置全文模式索引的分析器。索引是通过属性来创建，便于快速查找节点或者关系。创建和配置全文模式索引使用db.index.fulltext.createNodeIndex和db.index.fulltext.createRelationshipIndex创建全文模式索引。在创建索引时，每个索引必须为每个索引指定一个唯一的名称，用于在查询或删除索引时引用相关的特定索引。然后，全文模式索引分别应用于标签列表或关系类型列表，分别用于节点和关系索引，然后应用于属性名称列表。call db.index.fulltext.createNodeIndex("索引名",[Label,Label],[属性,属性]) call db.index.fulltext.createNodeIndex("nameAndDescription",["Person"],["name","description"]) call db.index.fulltext.queryNodes("nameAndDescription", "范闲") YIELD node, score RETURN node.name, node.description, score查看和删除索引call db.indexes 或者 :schema DROP INDEX ON :Person(name) DROP INDEX ON :Person(age, gender) call db.index.fulltext.drop("nameAndDescription")约束唯一性约束作用避免重复记录强制执行数据完整性规则注：如果某个字段创建了索引，则不能创建唯一性约束创建唯一性约束CREATE CONSTRAINT ON (变量:<label_name>) ASSERT 变量.<property_name> IS UNIQUE具体实例：CREATE CONSTRAINT ON (person:Person) ASSERT person.name IS UNIQUE删除唯一性约束DROP CONSTRAINT ON (cc:Person) ASSERT cc.name IS UNIQUE属性存在约束 (企业版中可用)CREATE CONSTRAINT ON (p:Person) ASSERT exists(p.name)查看约束call db.constraints :schema
CQL简介CQL代表Cypher查询语言。 像关系型数据库具有查询语言SQL，Neo4j使用CQL作为查询语言。Neo4j CQL它是Neo4j图形数据库的查询语言。它是一种声明性模式匹配语言。它遵循SQL语法。它的语法是非常简单且人性化、可读的格式。常用的Neo4j CQL命令/条款如下：S.No.CQL命令/条作用1CREATE 创建创建节点，关系和属性2MATCH 匹配检索有关节点，关系和属性数据3RETURN 返回返回查询结果4WHERE 条件提供条件过滤检索MATCH数据5DELETE 删除删除节点和关系6REMOVE 移除删除节点和关系的属性7SET 设置添加或更新标签8ORDER BY 排序对结果排序9SKIP LIMIT 分页分页10DISTINCT 排重排重完成上面的语法 我们基于庆余年电视剧人物关系图片:CREATECREATE ( <node-name>:<label-name> [{ <property1-name>:<property1-Value> ........ <propertyn-name>:<propertyn-Value> }] )语法说明：语法元素描述它是我们将要创建的节点名称。它是一个节点标签名称...属性是键值对。定义将分配给创建节点的属性的名称...属性是键值对。定义将分配给创建节点的属性的值举例：CREATE (person:Person) CREATE (person:Person {cid:1,name:"范闲",age:24,gender:0,character:"A",money:1000}); CREATE (person:Person {cid:2,name:"林婉儿",age:20,gender:1,character:"B",money:800}); CREATE (person:Person {cid:3,name:"庆帝",age:49,gender:0,character:"A",money:8900});MATCH RETURN命令语法MATCH ( <node-name>:<label-name> ) RETURN <node-name>.<property1-name>, ... <node-name>.<propertyn-name>语法元素描述它是我们将要创建的节点名称。它是一个节点标签名称...属性是键值对。定义将分配给创建节点的属性的名称举例：MATCH (person:Person) return person MATCH (person:Person) return person.name,person.age关系创建使用现有节点创建没有属性的关系MATCH (<node1-name>:<node1-label-name>),(<node2-name>:<node2-label-name>) CREATE (<node1-name>)-[<relationship-name>:<relationship-label-name>]->(<node2-name>) RETURN 相应的内容语法说明：S.No.语法元素描述1MATCH,CREATE,RETURN他们是Neo4J CQL关键字。2它用于创建关系的“From Node”的名称。3它用于创建关系的“From Node”的标签名称。4它用于创建关系的“To Node”的名称。5它用于创建关系的“To Node”的标签名称。6这是一个关系的名称。7它是一个关系的标签名称。8-表示无方向的关系9->表示有方向的关系，出到入/*创建关系*/ match(person:Person {name:"范闲"}) ,(person2:Person {name:"林婉儿"}) create(person)-[r:Couple]->(person2); /*查询关系*/ match p = (person:Person {name:"范闲"})-[r:Couple]->(person2:Person) return p match (p1:Person {name:"范闲"})-[r:Couple]-(p2:Person) return p1,p2 match (p1:Person {name:"范闲"})-[r:Couple]-(p2:Person) return r使用现有节点创建有属性的关系MATCH (<node1-label-name>:<node1-name>),(<node2-label-name>:<node2-name>) CREATE (<node1-label-name>)-[<relationship-label-name>:<relationship-name> {<define-properties-list>}]->(<node2-label-name>) RETURN <relationship-label-name> 其中<define-properties-list> 是分配给新创建关系的属性（名称 - 值对）的列表，格式如下。 { <property1-name>:<property1-value>, <property2-name>:<property2-value>, ... <propertyn-name>:<propertyn-value> }match(person:Person {name:"范闲"}),(person2:Person {name:"林婉儿"}) create(person)-[r:Couple{mary_date:"12/12/2014",price:55000}]->(person2) return r;使用新节点创建没有属性的关系CREATE (<node1-label-name>:<node1-name>) -[<relationship-label-name>:<relationship-name>] ->(<node1-label-name>:<node1-name>)create(person1:Person {cid:4,name:"长公主",age:49,gender:1,character:"A",money:5000}) -[r:Friend] ->(person2:Person {cid:7,name:"九品射手燕小乙",age:48,gender:0,character:"B",money:1000})使用新节点创建有属性的关系CREATE (<node1-label-name>:<node1-name>{<define-properties-list>}) -[<relationship-label-name>:<relationship-name>{<define-properties-list>}] ->(<node1-label-name>:<node1-name>{<define-properties-list>})create (person1:Person {cid:9,name:"靖王世子",age:23,gender:0,character:"A",money:3000}) -[r:Friend {date:"11-02-2000"}] ->(person2:Person {cid:8,name:"二皇子",age:24,gender:0,character:"B",money:6000})关系和节点的属性可以使用的类型IndexCQL数据类型作用1boolean它用于表示布尔文字：true, false。2byte它用来表示8位整数。3short它用于表示16位整数。4int它用于表示32位整数。5long它用于表示64位整数。6float浮点数用于表示32位浮点数。7doubleDouble用于表示64位浮点数。8charChar用于表示16位字符。9String字符串用于表示字符串。CREATE创建多个标签CREATE (<node-name>:<label-name1>:<label-name2>.....:<label-namen>)如:CREATE (person:Person:Beauty:Picture {cid:20,name:"小美女"})WHERE 子句简单的WHERE子句WHERE <condition>复杂的WHERE子句WHERE <condition> <boolean-operator> <condition>where 中的比较运算符 和 之前mysql的相同 如 = != <> > < 等S.No.布尔运算符描述1AND与2OR或3NOT非MATCH (person:Person) WHERE person.name = '范闲' OR person.name = '靖王世子' RETURN personDELETE 子句 和 REMOVE子句DELETE 子句删除节点。删除节点及相关节点和关系。match p = (:Person {name:"林婉儿"})-[r:Couple]-(:Person) delete rREMOVE子句删除节点或关系的标签删除节点或关系的属性MATCH (person:Person {name:"小美女"}) REMOVE person.cidSET子句向现有节点或关系添加新属性更新属性值MATCH (person:Person {cid:1}) SET person.money = 3456,person.age=25ORDER BY 子句“ORDER BY”子句，对MATCH查询返回的结果进行排序。我们可以按升序或降序对行进行排序。默认情况下，它按升序对行进行排序。 如果我们要按降序对它们进行排序，我们需要使用DESC子句。MATCH (person:Person) RETURN person.name,person.money ORDER BY person.money DESCSKIP 和 LIMITNeo4j CQL已提供“SKIP”子句来过滤或限制查询返回的行数。 它修整了CQL查询结果集顶部的结果。Neo4j CQL已提供“LIMIT”子句来过滤或限制查询返回的行数。 它修剪CQL查询结果集底部的结果。MATCH (person:Person) RETURN ID(person),person.name,person.money ORDER BY person.money DESC skip 4 limit 2DISTINCT 排重这个函数的用法就像SQL中的distinct关键字，返回的是所有不同值。MATCH (p:Person) RETURN Distinct(p.character)
图论图论起源 --- 柯尼斯堡（Konigsberg）七桥问题众所周知，图论起源于一个非常经典的问题——柯尼斯堡（Konigsberg）七桥问题。1738年，瑞典数学家欧拉( Leornhard Euler)解决了柯尼斯堡七桥问题。由此图论诞生，欧拉也成为图论的创始人。欧拉把问题的实质归于"一笔画"问题，即判断一个图是否能够遍历完所有的边(Edge)而没有重复，而柯尼斯堡七桥问题则是一笔画问题的一个具体情境。欧拉证明这个问题不成立。满足一笔画的图满足两个条件:图必须是一个完整图有零个或二个奇数点图和节点图是一组节点和连接这些节点的关系组成。图形数据存储在节点和关系所在的属性上。属性是键值对表示的数据。在图形理论中，我们可以使用圆表示一个节点 并且可以向里面添加键值对形式的数据。节点关系表达简单关系表达此处在两个节点之间创建关系名称“跟随”。 这意味着Profile1 跟随 Profile2。复杂关系表达这里节点用关系连接。 关系是单向或双向的。从XYZ到PQR的关系是单向关系。从ABC到XYZ的关系是双向关系。属性图模型规则图表示节点，关系和属性中的数据节点和关系都包含属性关系连接节点属性是键值对节点用圆圈表示，关系用方向键表示。关系具有方向：单向和双向。每个关系包含“开始节点”或“从节点” 和 “到节点”或“结束节点”知识图谱和图库知识图谱一种基于图的数据结构，由节点(Node)和边(Edge)组成。其中节点即实体，由一个全局唯一的ID标示，边就是关系用于连接两个节点。通俗地讲，知识图谱就是把所有不同种类的信息（Heterogeneous Information）连接在一起而得到的一个关系网络。知识图谱提供了从“关系”的角度去分析问题的能力。互联网、大数据的背景下，谷歌、百度、搜狗等搜索引擎纷纷基于该背景，创建自己的知识图谱Knowledge Graph（谷歌）、知心（百度）和知立方（搜狗），主要用于改进搜索质量。图数据库一般情况下，我们使用数据库查找事物间的联系的时候，只需要短程关系的查询（两层以内的关联）。当需要进行更长程的，更广范围的关系查询时，就需要图数据库的功能。而随着社交、电商、金融、零售、物联网等行业的快速发展，现实世界的事物之间织起了一张巨大复杂的关系网，传统数据库面对这样复杂关系往往束手无策。因此，图数据库应运而生。图数据库(Graph database)指的是以图数据结构的形式来存储和查询数据的数据库。知识图谱中，知识的组织形式采用的就是图结构，所以非常适合用图库进行存储。图形数据库优势在需要表示多对多关系时，我们常常需要创建一个关联表来记录不同实体的多对多关系。如果两个实体之间拥有多种关系，那么我们就需要在它们之间创建多个关联表。而在一个图形数据库中，我们只需要标明两者之间存在着不同的关系。如果希望在两个结点集间建立双向关系，我们就需要为每个方向定义一个关系。 也就是说，相对于关系型数据库中的各种关联表，图形数据库中的关系可以通过关系属性这一功能来提供更为丰富的关系展现方式。因此相较于关系型数据库，图形数据库的用户在对现实进行抽象时将拥有一个额外的武器，那就是丰富的关系。优势总结:性能上，对长程关系的查询速度快擅于发现隐藏的关系，例如通过判断图上两点之间有没有走的通的路径，就可以发现事物间的关联Neo4j 基础什么是Neo4jNeo4j是一个开源的 无Shcema的 基于java开发的 图形数据库，它将结构化数据存储在图中而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎。程序数据是在一个面向对象的、灵活的网络结构下，而不是严格、静态的表中,但可以享受到具备完全的事务特性、企业级的数据库的所有好处。这个链接是各数据库市场得分：https://db-engines.com/en/rankingNeo4j 模块构建Neo4j 主要构建块节点属性关系标签数据浏览器节点节点是图表的基本单位。 它包含具有键值对的属性属性属性是用于描述图节点和关系的键值对Key =值其中Key是一个字符串值可以通过使用任何Neo4j数据类型来表示关系关系是图形数据库的另一个主要构建块。 它连接两个节点，如下所示。这里Emp和Dept是两个不同的节点。 “WORKS_FOR”是Emp和Dept节点之间的关系。因为它表示从Emp到Dept的箭头标记，那么这种关系描述的一样Emp WORKS_FOR Dept每个关系包含一个起始节点和一个结束节点。这里“Emp”是一个起始节点。“Dept”是端节点。由于该关系箭头标记表示从“Emp”节点到“Dept”节点的关系，该关系被称为“进入关系”到“Dept”节点。并且“外向关系”到“Emp”节点。像节点一样，关系也可以包含属性作为键值对。标签Label将一个公共名称与一组节点或关系相关联。 节点或关系可以包含一个或多个标签。 我们可以为现有节点或关系创建新标签。 我们可以从现有节点或关系中删除现有标签。从前面的图中，我们可以观察到有两个节点。左侧节点都有一个标签：“EMP”，而右侧节点都有一个标签：“Dept”。这两个节点之间的关系，也有一个标签：“WORKS_FOR”注：Neo4j将数据存储在节点或关系的属性中。Neo4j的主要应用场景社交媒体和社交网络当使用图形数据库为社交网络应用程序提供动力时，可以轻松利用社交关系或根据活动推断关系。查询社区聚类分析，朋友的朋友推荐，影响者分析，共享和协作关系分析等推荐引擎和产品推荐系统图形驱动的推荐引擎通过实时利用多种连接，帮助公司个性化产品，内容和服务。内容和媒体推荐，图形辅助搜索引擎，产品推荐，专业网络，社会推荐。身份和访问管理使用图形数据库进行身份和访问管理时，可以快速有效地跟踪用户，资产，关系和授权。查询访问管理，资产来源，数据所有权，身份管理，互连组织，主数据，资源授权金融反欺诈多维关联分析场景通过图分析可以清楚地知道洗钱网络及相关嫌疑，例如对用户所使用的帐号、发生交易时的IP地址、MAC地址、手机IMEI号等进行关联分析。Neo4j 环境搭建Neo4j环境Linux下搭建切换到Linux下 到安装目录neo4j 上传安装包 或者 下载安装包使用 ftp 工具上传neo4j-community-3.5.17.tar 到 liunx 下或者wget https://neo4j.com/artifact.php?name=neo4j-community-3.5.17-unix.tar.gz解压tar -xvf neo4j-community-3.5.17.tar修改配置文件 neo4j.confvi conf/neo4j.conf主要是修改 允许远程访问的地址 把对应的注释打开即可dbms.connectors.default_listen_address=0.0.0.0开放对应的访问端口 默认要开放7474 和 7687firewall-cmd --zone=public --add-port=7474/tcp --permanent firewall-cmd --zone=public --add-port=7687/tcp --permanent systemctl reload firewalld启动./bin/neo4j start使用浏览器 访问服务器上的 neo4jhttp://192.168.211.133:7474默认的账号是 neo4j 密码 neo4j 这里第一次登录的话会要求修改密码Windows下的安装从https://neo4j.com/download-center/#community 下载最新的Neo4j Server安装文件可以看到 neo4J 软件 exe 或 zip 格式的所有版本下载 Neo4j 3.5.17 (zip)解压修改配置文件dbms.connectors.default_listen_address=0.0.0.0通过 neo4j.bat install-service 安装neo4j服务注意的问题 如果是4.0 以及以上版本需要jdk11修改文件 bin/neo4j.ps1Import-Module "neo4j的主目录\bin\Neo4j-Management.psd1"neo4j.bat启动neo4j.bat start使用浏览器访问服务器上的 neo4jhttp://127.0.0.1:7474默认的账号是 neo4j 密码 neo4j 这里第一次登录的话会要求修改密码Neo4j数据浏览器数据浏览器访问一旦我们安装Neo4j，我们可以访问Neo4j数据浏览器使用以下URLhttp://localhost:7474/browser/Neo4j数据浏览器用于执行CQL命令并查看输出输出。这里我们需要在美元提示符处执行所有CQL命令：“$” 如CREATE(cc:CreditCard)在美元符号后键入命令，然后单击“执行”按钮运行命令。注：默认没有开启多条语句同时执行，如果需要，就在数据浏览器设置里面开启。它与Neo4j数据库服务器交互，检索和显示下面的结果到那个美元提示。使用“VI视图”按钮以图形格式查看结果。 上图以“UI视图”格式显示结果。导出 CSV 或者 JSON单击“导出CSV”按钮以csv文件格式导出结果
安全认证概述MongoDB 默认是没有账号的，可以直接连接，无须身份验证。实际项目中肯定是要权限验证的，否则后果不堪设想。从2016年开始，发生了多起MongoDB黑客赎金事件，大部分MongoDB安全问题暴露出了安全问题的短板其实是用户，首先用户对于数据库的安全不重视，其次用户在使用过程中可能没有养成定期备份的好习惯，最后是企业可能缺乏有经验和技术的专业人员。所以对MongoDB进行安全认证是必须要做的。用户相关操作切换到admin数据库对用户的添加use admin; /*用于创建 MongoDB 登录用户以及分配权限的方法*/ db.createUser(userDocument)db.createUser( { user: "账号", pwd: "密码", roles: [ { role: "角色", db: "安全认证的数据库" }, { role: "角色", db: "安全认证的数据库" } ] } ) user：创建的用户名称，如 admin、root 、xxxpwd：用户登录的密码roles：为用户分配的角色，不同的角色拥有不同的权限，参数是数组，可以同时设置多个role：角色，MonngoDB 已经约定好的角色，不同的角色对应不同的权限 后面会对role做详细解释db：数据库实例名称，如 MongoDB 4.0.2 默认自带的有 admin、local、config、test 等，即为哪个数据库实例设置用户举例:use admin db.createUser( { user:"root", pwd:"123321", roles:[{role:"root",db:"admin"}] } )修改密码db.changeUserPassword( 'root' , 'rootNew' );用户添加角色db.grantRolesToUser( '用户名' , [{ role: '角色名' , db: '数据库名'}])以auth启动mongod./bin/mongod -f conf/mongo.conf --auth也可以在 mongo.conf 中添加 auth=true 参数验证用户db.auth("账号","密码")删除用户db.dropUser("用户名")角色数据库内置的角色read：允许用户读取指定数据库readWrite：允许用户读写指定数据库dbAdmin：允许用户在指定数据库中执行管理函数，如索引创建、删除，查看统计或访问system.profileuserAdmin：允许用户向system.users集合写入，可以找指定数据库里创建、删除和管理用户clusterAdmin：只在admin数据库中可用，赋予用户所有分片和复制集相关函数的管理权限readAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的读权限readWriteAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的读写权限userAdminAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的userAdmin权限dbAdminAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的dbAdmin权限root：只在admin数据库中可用。超级账号，超级权限dbOwner：库拥有者权限，即readWrite、dbAdmin、userAdmin角色的合体各个类型用户对应的角色数据库用户角色：read、readWrite数据库管理角色：dbAdmin、dbOwner、userAdmin集群管理角色：clusterAdmin、clusterManager、clusterMonitor、hostManager备份恢复角色：backup、restore；所有数据库角色：readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase超级用户角色：root这里还有几个角色间接或直接提供了系统超级用户的访问（dbOwner 、userAdmin、userAdminAnyDatabase）单机安全认证实现流程创建 mydb1 数据库并创建了两个用户，zhangsan 拥有读写权限，lisi 拥有只读权限测试这两个账户的权限。以超级管理员登录测试权限。创建管理员MongoDB 服务端开启安全检查之前，至少需要有一个管理员账号，admin 数据库中的用户都被视为管理员。如果 admin 库没有任何用户的话，即使在其他数据库中创建了用户，启用身份验证，默认的连接方式依然会有超级权限，即仍然可以不验证账号密码照样能进行 CRUD，安全认证相当于无效。>use admin switched to db admin > db admin > db.createUser( ... { ... user:"root", ... pwd:"123456", ... roles:[{role:"root",db:"admin"}] ... })创建普通用户如下所示 mydb1 是自己新建的数据库，没安全认证之前可以随意 CRUD，其余的都是 mongoDB 4.0.2 自带的数据库>show dbs admin 0.000GB config 0.000GB local 0.000GB mydb1 0.000GB > use mydb1 switched to db mydb1 > db.c1.insert({name:"testdb1"}) > db.c1.insert({name:"testdb1"}) > show tables c1 c2 > db.c1.find() ...为 admin 库创建管理员之后，现在来为 普通数据库创建普通用户，以 mydb1 为例，方式与创建管理员一致，切换到指定数据库进行创建即可。如下所示，为 mydb1 数据库创建了两个用户，zhangsan 拥有读写权限，lisi 拥有只读权限，密码都是 123456.> use mydb1 switched to db mydb1 > db mydb1 > db.createUser({ ... user:"zhangsan", ... pwd:"123456", ... roles:[{role:"readWrite",db:"mydb1"}] ... }) > db.createUser({ ... user:"lisi", ... pwd:"123456", ... roles:[{role:"read",db:"mydb1"}] ... })接着从客户端关闭 MongoDB 服务端，之后服务端会以安全认证方式进行启动> use admin switched to db admin > db.shutdownServer() server should be down...MongoDB 安全认证方式启动mongod --dbpath=数据库路径 --port=端口 --auth也可以在配置文件中 加入 auth=true分别以普通用户登录验证权限普通用户现在仍然像以前一样进行登录，如下所示直接登录进入 mydb1 数据库中，登录是成功的，只是登录后日志少了很多东西，而且执行 show dbs 命令，以及 show tables 等命令都是失败的，即使没有被安全认证的数据库，用户同样操作不了，这都是因为权限不足，一句话：用户只能在自己权限范围内的数据库中进行操作./bin/mongo localhost:57017/mydb1 > show dbs如下所示，登录之后必须使用 db.auth("账号","密码") 方法进行安全认证，认证通过，才能进行权限范围内的操作> db.auth("zhangsan","123456") 1 > show dbs mydb1 0.000GB > show tables c1 c2以管理员登录验证权限客户端管理员登录如下所示 管理员 root 登录，安全认证通过后，拥有对所有数据库的所有权限。./bin/mongo localhost:57017 > use admin switched to db admin > db.auth("root","root") 1 > show dbs ...分片集群安全认证开启安全认证之前，进入路由创建管理员和普通用户完全参考上面的 单机安全认证实现流程 即可use mg db.createUser({ ... user:"xxx", ... pwd:"123456", ... roles:[{role:"readWrite",db:"mg"}] ... })关闭所有的配置节点、分片节点和路由节点安装psmiscyum install psmisc安装完之后可以使用killall命令快速关闭多个进程killall mongod生成密钥文件并修改权限openssl rand -base64 756 > data/mongodb/testKeyFile.file chmod 600 data/mongodb/keyfile/testKeyFile.file配置节点集群和分片节点集群的配置文件开启安全认证和指定密钥文件auth=true keyFile=data/mongodb/testKeyFile.file在路由节点配置文件中设置密钥文件keyFile=data/mongodb/testKeyFile.file启动所有的配置节点 分片节点 和 路由节点 使用路由进行权限验证可以编写一个shell脚本批量启动./bin/mongod -f config/config-17017.conf ./bin/mongod -f config/config-17018.conf ./bin/mongod -f config/config-17019.conf ./bin/mongod -f shard/shard1/shard1-37017.conf ./bin/mongod -f shard/shard1/shard1-37018.conf ./bin/mongod -f shard/shard1/shard1-37019.conf ./bin/mongod -f shard/shard2/shard2-47017.conf ./bin/mongod -f shard/shard2/shard2-47018.conf ./bin/mongod -f shard/shard2/shard2-47019.conf ./bin/mongos -f route/route-27017.confSpring boot 连接安全认证的分片集群spring.data.mongodb.username=账号 spring.data.mongodb.password=密码 #spring.data.mongodb.uri=mongodb://账号:密码@IP:端口/数据库名
MongoDB主从复制架构原理和缺陷master-slave架构中master节点负责数据的读写，slave没有写入权限只负责读取数据。在主从结构中，主节点的操作记录成为oplog（operation log）。oplog存储在系统数据库local的oplog.$main集合中，这个集合的每个文档都代表主节点上执行的一个操作。从服务器会定期从主服务器中获取oplog记录，然后在本机上执行！对于存储oplog的集合，MongoDB采用的是固定集合，也就是说随着操作过多，新的操作会覆盖旧的操作！主从结构没有自动故障转移功能，需要指定master和slave端，不推荐在生产中使用。mongodb4.0后不再支持主从复制！[main] Master/slave replication is no longer supported复制集replica sets什么是复制集复制集是由一组拥有相同数据集的mongod实例做组成的集群。复制集是一个集群，它是2台及2台以上的服务器组成，以及复制集成员包括Primary主节点,secondary从节点和投票节点。复制集提供了数据的冗余备份，并在多个服务器上存储数据副本，提高了数据的可用性，保证数据的安全性。为什么要使用复制集高可用防止设备（服务器、网络）故障。提供自动failover 功能。技术来保证高可用灾难恢复当发生故障时，可以从其他节点恢复，用于备份。功能隔离我们可以在备节点上执行读操作，减少主节点的压力，比如:用于分析、报表，数据挖掘，系统任务等。复制集集群架构原理一个复制集中Primary节点上能够完成读写操作，Secondary节点仅能用于读操作。Primary节点需要记录所有改变数据库状态的操作，这些记录保存在 oplog 中，这个文件存储在 local 数据库，各个Secondary节点通过此 oplog 来复制数据并应用于本地，保持本地的数据与主节点的一致。oplog 具有幂等性，即无论执行几次其结果一致，这个比 mysql 的二进制日志更好用。oplog的组成结构{ "ts" : Timestamp(1446011584, 2), "h" : NumberLong("1687359108795812092"), "v" : 2, "op" : "i", "ns" : "test.nosql", "o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "10"} }ts：操作时间，当前timestamp + 计数器，计数器每秒都被重置h：操作的全局唯一标识v：oplog版本信息op：操作类型 i：插入操作 u：更新操作 d：删除操作 c：执行命令（如createDatabase，dropDatabase）n：空操作，特殊用途ns：操作针对的集合o：操作内容o2：更新查询条件，仅update操作包含该字段复制集数据同步分为初始化同步和keep复制同步。初始化同步指全量从主节点同步数据，如果Primary节点数据量比较大同步时间会比较长。而keep复制指初始化同步过后，节点之间的实时同步一般是增量同步。初始化同步有以下两种情况会触发：(1) Secondary第一次加入。(2) Secondary落后的数据量超过了oplog的大小，这样也会被全量复制。MongoDB的Primary节点选举基于心跳触发。一个复制集N个节点中的任意两个节点维持心跳，每个节点维护其他N-1个节点的状态。心跳检测：整个集群需要保持一定的通信才能知道哪些节点活着哪些节点挂掉。mongodb节点会向副本集中的其他节点每2秒就会发送一次pings包，如果其他节点在10秒钟之内没有返回就标示为不能访问。每个节点内部都会维护一个状态映射表，表明当前每个节点是什么角色、日志时间戳等关键信息。如果主节点发现自己无法与大部分节点通讯则把自己降级为secondary只读节点。主节点选举触发的时机:第一次初始化一个复制集Secondary节点权重比Primary节点高时，发起替换选举Secondary节点发现集群中没有Primary时，发起选举Primary节点不能访问到大部分(Majority)成员时主动降级当触发选举时，Secondary节点尝试将自身选举为Primary。主节点选举是一个二阶段过程+多数派协议。第一阶段:检测自身是否有被选举的资格，如果符合资格会向其它节点发起本节点是否有选举资格的FreshnessCheck，进行同僚仲裁第二阶段:发起者向集群中存活节点发送Elect(选举)请求，仲裁者收到请求的节点会执行一系列合法性检查，如果检查通过，则仲裁者(一个复制集中最多50个节点 其中只有7个具有投票权)给发起者投一票。pv0通过30秒选举锁防止一次选举中两次投票。pv1使用了terms(一个单调递增的选举计数器)来防止在一次选举中投两次票的情况。多数派协议:发起者如果获得超过半数的投票，则选举通过，自身成为Primary节点。获得低于半数选票的原因，除了常见的网络问题外，相同优先级的节点同时通过第一阶段的同僚仲裁并进入第二阶段也是一个原因。因此，当选票不足时，会sleep[0,1]秒内的随机时间，之后再次尝试选举。复制集搭建主节点配置 mongo_37017.conf# 主节点配置 dbpath=/data/mongo/data/server1 bind_ip=0.0.0.0 port=37017 fork=true logpath=/data/mongo/logs/server1.log replSet=mgCluster从节点1配置 mongo_37018.confdbpath=/data/mongo/data/server2 bind_ip=0.0.0.0 port=37018 fork=true logpath=/data/mongo/logs/server2.log replSet=mgCluster从节点2配置 mongo_37019.confdbpath=/data/mongo/data/server3 bind_ip=0.0.0.0 port=37019 fork=true logpath=/data/mongo/logs/server3.log replSet=mgCluster初始化节点配置启动三个节点，然后进入任意一个节点，运行如下命令：var cfg ={"_id":"mgCluster", "protocolVersion" : 1, "members":[ {"_id":1,"host":"192.168.211.133:37017","priority":10}, {"_id":2,"host":"192.168.211.133:37018"} ] } rs.initiate(cfg) rs.status()节点的动态增删增加节点rs.add("192.168.211.133:37019")删除slave节点rs.remove("192.168.211.133:37019")复制集操作演示进入主节点 ----- 插入数据 ------ 进入从节点验证注意：默认节点下从节点不能读取数据。调用 rs.slaveOk() 解决为了保证高可用，在集群当中如果主节点挂掉后，会自动在从节点中选举一个，重新做为主节点。/*查看节点状态*/ rs.status()节点说明:PRIMARY 节点： 可以查询和新增数据SECONDARY 节点：只能查询，不能新增，基于priority 权重可以被选为主节点ARBITER 节点： 不能查询数据和新增数据，不能变成主节点复制集成员的配置参数参数字段类型说明取值说明_id整数_id:0复制集中的标示host字符串host:"主机:端口"节点主机名arbiterOnly布尔值arbiterOnly:true是否为仲裁(裁判)节点priority（权重）整数priority=0或1默认1，是否有资格变成主节点，取值范围0-1000，0永远不会变成主节点hidden布尔值hidden=true或false，0或1隐藏，权重必须为0，才可以设置votes整数votes= 0或1投票，是否为投票节点,0 不投票，1投票slaveDelay整数slaveDelay=3600从库的延迟多少秒buildIndexes布尔值buildIndexes=true或false，0或1主库的索引，从库也创建，_id索引无效举例：var cfg ={"_id":"mgCluster", "protocolVersion" : 1, "members":[ {"_id":1,"host":"192.168.211.133:37017","priority":10}, {"_id":2,"host":"192.168.211.133:37018","priority":0}, {"_id":3,"host":"192.168.211.133:37019","priority":5}, {"_id":4,"host":"192.168.211.133:37020","arbiterOnly":true} ] }; /*重新装载配置，并重新生成集群节点。*/ rs.reconfig(cfg) /*重新查看集群状态*/ rs.status()有仲裁节点复制集搭建和上面的配置步骤相同，只是增加了一个特殊的仲裁节点注入节点执行rs.addArb("IP:端口")rs.addArb("192.168.211.133:37020")分片集群 Shard Cluster什么是分片分片（sharding）是MongoDB用来将大型集合水平分割到不同服务器（或者复制集）上所采用的方法。不需要功能强大的大型计算机就可以存储更多的数据，处理更大的负载。为什么要分片存储容量需求超出单机磁盘容量。活跃的数据集超出单机内存容量，导致很多请求都要从磁盘读取数据，影响性能。IOPS超出单个MongoDB节点的服务能力，随着数据的增长，单机实例的瓶颈会越来越明显。副本集具有节点数量限制。垂直扩展：增加更多的CPU和存储资源来扩展容量。水平扩展：将数据集分布在多个服务器上。水平扩展即分片。分片的工作原理分片集群由以下3个服务组成：Shards Server: 每个shard由一个或多个mongod进程组成，用于存储数据。Router Server: 数据库集群的请求入口，所有请求都通过Router(mongos)进行协调，不需要在应用程序添加一个路由选择器，Router(mongos)就是一个请求分发中心它负责把应用程序的请求转发到对应的Shard服务器上。Config Server: 配置服务器。存储所有数据库元信息（路由、分片）的配置。片键（shard key）为了在数据集合中分配文档，MongoDB使用分片主键分割集合。区块（chunk）在一个shard server内部，MongoDB还是会把数据分为chunks，每个chunk代表这个shard server内部一部分数据。MongoDB分割分片数据到区块，每一个区块包含基于分片主键的左闭右开的区间范围。分片策略范围分片（Range based sharding）范围分片是基于分片主键的值切分数据，每一个区块将会分配到一个范围。范围分片适合满足在一定范围内的查找，例如查找X的值在[20,30)之间的数据，mongo 路由根据Config server中存储的元数据，可以直接定位到指定的shard的Chunk中。缺点: 如果shard key有明显递增（或者递减）趋势，则新插入的文档多会分布到同一个chunk，无法扩展写的能力。hash分片（Hash based sharding）Hash分片是计算一个分片主键的hash值，每一个区块将分配一个范围的hash值。Hash分片与范围分片互补，能将文档随机的分散到各个chunk，充分的扩展写能力，弥补了范围分片的不足，缺点是不能高效的服务范围查询，所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。组合片键 A + B(散列思想 不能是直接hash)数据库中没有比较合适的片键供选择，或者是打算使用的片键基数太小（即变化少如星期只有7天可变化），可以选另一个字段使用组合片键，甚至可以添加冗余字段来组合。一般是粗粒度+细粒度进行组合。合理的选择shard key无非从两个方面考虑，数据的查询和写入，最好的效果就是数据查询时能命中更少的分片，数据写入时能够随机的写入每个分片，关键在于如何权衡性能和负载。分片集群的搭建过程配置并启动 config 节点集群节点1 config-17017.conf# 数据库文件位置 dbpath=config/config1 #日志文件位置 logpath=config/logs/config1.log # 以追加方式写入日志 logappend=true # 是否以守护进程方式运行 fork = true bind_ip=0.0.0.0 port = 17017 # 表示是一个配置服务器 configsvr=true #配置服务器副本集名称 replSet=configsvr节点2 config-17018.conf# 数据库文件位置 dbpath=config/config2 #日志文件位置 logpath=config/logs/config.log # 以追加方式写入日志 logappend=true # 是否以守护进程方式运行 fork = true bind_ip=0.0.0.0 port = 17018 # 表示是一个配置服务器 configsvr=true #配置服务器副本集名称 replSet=configsvr节点3 config-17019.conf# 数据库文件位置 dbpath=config/config3 #日志文件位置 logpath=config/logs/config3.log # 以追加方式写入日志 logappend=true # 是否以守护进程方式运行 fork = true bind_ip=0.0.0.0 port = 17019 # 表示是一个配置服务器 configsvr=true #配置服务器副本集名称 replSet=configsvr启动配置节点./bin/mongod -f config/config-17017.conf ./bin/mongod -f config/config-17018.conf ./bin/mongod -f config/config-17019.conf进入任意节点的mongo shell并添加配置节点集群，注意use admin./bin/mongo --port 17017use admin var cfg ={"_id":"configsvr", "members":[ {"_id":1,"host":"192.168.211.133:17017"}, {"_id":2,"host":"192.168.211.133:17018"}, {"_id":3,"host":"192.168.211.133:17019"} ] }; rs.initiate(cfg)配置shard集群shard1集群搭建37017到37019dbpath=shard/shard1/shard1-37017 bind_ip=0.0.0.0 port=37017 fork=true logpath=shard/shard1/shard1-37017.log replSet=shard1 shardsvr=true dbpath=shard/shard1/shard1-37018 bind_ip=0.0.0.0 port=37018 fork=true logpath=shard/shard1/logs/shard1-37018.log replSet=shard1 shardsvr=true dbpath=shard/shard1/shard1-37019 bind_ip=0.0.0.0 port=37019 fork=true logpath=shard/shard1/logs/shard1-37019.log replSet=shard1 shardsvr=true启动每个 mongod 然后进入其中一个进行集群配置var cfg ={"_id":"shard1", "protocolVersion" : 1, "members":[ {"_id":1,"host":"192.168.211.133:37017"}, {"_id":2,"host":"192.168.211.133:37018"}, {"_id":3,"host":"192.168.211.133:37019"} ] }; rs.initiate(cfg) rs.status()shard2集群搭建47017到47019dbpath=shard/shard2/shard2-47017 bind_ip=0.0.0.0 port=47017 fork=true logpath=shard/shard2/logs/shard2-47017.log replSet=shard2 shardsvr=true dbpath=shard/shard2/shard2-47018 bind_ip=0.0.0.0 port=47018 fork=true logpath=shard/shard2/logs/shard2-47018.log replSet=shard2 shardsvr=true dbpath=shard/shard2/shard2-47019 bind_ip=0.0.0.0 port=47019 fork=true logpath=shard/shard2/logs/shard2-47019.log replSet=shard2 shardsvr=true启动每个 mongod 然后进入其中一个进行集群配置var cfg ={"_id":"shard2", "protocolVersion" : 1, "members":[ {"_id":1,"host":"192.168.211.133:47017"}, {"_id":2,"host":"192.168.211.133:47018"}, {"_id":3,"host":"192.168.211.133:47019"} ] }; rs.initiate(cfg) rs.status()配置和启动路由节点route-27017.confport=27017 bind_ip=0.0.0.0 fork=true logpath=route/logs/route.log configdb=configsvr/192.168.211.133:17017,192.168.211.133:17018,192.168.211.133:17019启动路由节点使用 mongos （注意不是mongod）./bin/mongos -f route/route-27017.confmongos（路由）中添加分片节点进入路由mongos./bin/mongo --port 27017sh.status() sh.addShard("shard1/192.168.211.133:37017,192.168.211.133:37018,192.168.211.133:37019"); sh.addShard("shard2/192.168.211.133:47017,192.168.211.133:47018,192.168.211.133:47019"); sh.status()开启数据库和集合分片(指定片键)继续使用mongos完成分片开启和分片大小设置/*为数据库开启分片功能*/ sh.enableSharding("mg") /*为指定集合开启分片功能*/ sh.shardCollection("mg.mg_datas",{"片键字段名，如name":索引说明})向集合中插入数据测试通过路由循环向集合中添加数use mg; for(var i=1;i<= 1000;i++){ db.mg_datas.insert({"name":"test"+i, salary:(Math.random()*20000).toFixed(2)}); }验证分片效果分别进入 shard1 和 shard2 中的数据库进行验证
MongoDB逻辑结构MongoDB 与 MySQL 中的架构相差不多，底层都使用了可插拔的存储引擎以满足用户的不同需要。用户可以根据程序的数据特征选择不同的存储引擎,在最新版本的 MongoDB 中使用了 WiredTiger 作为默认的存储引擎，WiredTiger 提供了不同粒度的并发控制和压缩机制，能够为不同种类的应用提供了最好的性能和存储率。在存储引擎上层的就是 MongoDB 的数据模型和查询语言了，由于 MongoDB 对数据的存储与 RDBMS 有较大的差异，所以它创建了一套不同的数据模型和查询语言。MongoDB的数据模型描述数据模型内嵌内嵌的方式指的是把相关联的数据保存在同一个文档结构之中。MongoDB的文档结构允许一个字段或者一个数组内的值作为一个嵌套的文档。引用引用方式通过存储数据引用信息来实现两个不同文档之间的关联,应用程序可以通过解析这些数据引用来访问相关数据。如何选择数据模型选择内嵌:1）数据对象之间有包含关系 ,一般是数据对象之间有一对多或者一对一的关系 。2）需要经常一起读取的数据。3）有 map-reduce/aggregation 需求的数据放在一起，这些操作都只能操作单个 collection。选择引用:1）当内嵌数据会导致很多数据的重复，并且读性能的优势又不足于覆盖数据重复的弊端 。2）需要表达比较复杂的多对多关系的时候 。3）大型层次结果数据集 嵌套不要太深。MongoDB存储引擎存储引擎概述存储引擎是MongoDB的核心组件，负责管理数据如何存储在硬盘和内存上。MongoDB支持的存储引擎有MMAPv1 ,WiredTiger和InMemory。InMemory存储引擎用于将数据只存储在内存中，只将少量的元数据(meta-data)和诊断日志（Diagnostic）存储到硬盘文件中，由于不需要Disk的IO操作，就能获取所需的数据，InMemory存储引擎大幅度降低了数据查询的延迟（Latency）。从mongodb3.2开始默认的存储引擎是WiredTiger,3.2版本之前的默认存储引擎是MMAPv1，mongodb4.x版本不再支持MMAPv1存储引擎。storage: journal: enabled: true dbPath: /data/mongo/ ##是否一个库一个文件夹 directoryPerDB: true ##数据引擎 engine: wiredTiger ##WT引擎配置 WiredTiger: engineConfig: ##WT最大使用cache（根据服务器实际情况调节） cacheSizeGB: 2 ##是否将索引也按数据库名单独存储 directoryForIndexes: true journalCompressor:none （默认snappy） ##表压缩配置 collectionConfig: blockCompressor: zlib (默认snappy,还可选none、zlib) ##索引配置 indexConfig: prefixCompression: trueWiredTiger存储引擎优势文档空间分配方式WiredTiger使用的是BTree存储，MMAPV1是线性存储，需要Padding并发级别WiredTiger文档级别锁，MMAPV1引擎使用表级锁数据压缩snappy (默认) 和 zlib，相比MMAPV1(无压缩) 空间节省数倍。内存使用WiredTiger 可以指定内存的使用大小。Cache使用WT引擎使用了二阶缓存WiredTiger Cache，File System Cache来保证Disk上的数据的最终一致性。而 MMAPv1 只有 journal 日志。WiredTiger引擎包含的文件和作用WiredTiger.basecfg: 存储基本配置信息，与 ConfigServer有关系WiredTiger.lock: 定义锁操作table*.wt: 存储各张表的数据WiredTiger.wt: 存储table* 的元数据WiredTiger.turtle: 存储WiredTiger.wt的元数据journal: 存储WAL(Write Ahead Log)WiredTiger存储引擎实现原理写请求WiredTiger的写操作会默认写入Cache ,并持久化到WAL (Write Ahead Log)，每60s或Log文件达到2G做一次checkpoint (当然我们也可以通过在写入时传入 j: true 的参数强制 journal 文件的同步，writeConcern{ w: , j: , wtimeout: }) 产生快照文件。WiredTiger初始化时，恢复至最新的快照状态，然后再根据WAL恢复数据，保证数据的完整性。Cache是基于BTree的，节点是一个page，root page是根节点，internal page是中间索引节点，leaf page真正存储数据，数据以page为单位读写。WiredTiger采用Copy on write的方式管理写操作（insert、update、delete），写操作会先缓存在cache里，持久化时，写操作不会在原来的leaf page上进行，而是写入新分配的page，每次checkpoint都会产生一个新的root page。checkpoint流程对所有的table进行一次checkpoint，每个table的checkpoint的元数据更新至WiredTiger.wt对WiredTiger.wt进行checkpoint，将该table checkpoint的元数据更新至临时文件WiredTiger.turtle.set将WiredTiger.turtle.set重命名为WiredTiger.turtle。上述过程如果中间失败，WiredTiger在下次连接初始化时，首先将数据恢复至最新的快照状态，然后根据WAL恢复数据，以保证存储可靠性。Journaling在数据库宕机时 , 为保证 MongoDB 中数据的持久性，MongoDB 使用了 Write Ahead Logging 向磁盘上的 journal 文件预先进行写入。除了 journal 日志，MongoDB 还使用检查点（checkpoint）来保证数据的一致性，当数据库发生宕机时，我们就需要 checkpoint 和 journal 文件协作完成数据的恢复工作。在数据文件中查找上一个检查点的标识符在 journal 文件中查找标识符对应的记录重做对应记录之后的全部操作
MongoDB的适用场景网站数据：Mongo 非常适合实时的插入,更新与查询，并具备网站实时数据存储所需的复制及高度伸缩性。缓存：由于性能很高，Mongo 也适合作为信息基础设施的缓存层。在系统重启之后，由Mongo搭建的持久化缓存层可以避免下层的数据源过载。大尺寸、低价值的数据：使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费，在此之前，很多时候程序员往往会选择传统的文件进行存储。高伸缩性的场景：Mongo 非常适合由数十或数百台服务器组成的数据库，Mongo 的路线图中已经包含对MapReduce 引擎的内置支持以及集群高可用的解决方案。用于对象及JSON 数据的存储：Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。MongoDB的行业具体应用场景游戏场景，使用 MongoDB 存储游戏用户信息，用户的装备、积分等直接以内嵌文档的形式存储，方便查询、更新。物流场景，使用 MongoDB 存储订单信息，订单状态在运送过程中会不断更新，以 MongoDB 内嵌数组的形式来存储，一次查询就能将订单所有的变更读取出来。社交场景，使用 MongoDB 存储存储用户信息，以及用户发表的朋友圈信息，通过地理位置索引实现附近的人、地点等功能。物联网场景，使用 MongoDB 存储所有接入的智能设备信息，以及设备汇报的日志信息，并对这些信息进行多维度的分析。直播，使用 MongoDB 存储用户信息、礼物信息等。如何抉择是否使用MongoDB应用特征Yes/No应用不需要事务及复杂 join 支持必须Yes新应用，需求会变，数据模型无法确定，想快速迭代开发？应用需要2000-3000以上的读写QPS（更高也可以）？应用需要TB甚至 PB 级别数据存储？应用发展迅速，需要能快速水平扩展？应用要求存储的数据不丢失？应用需要99.999%高可用？应用需要大量的地理位置查询、文本查询？一般情况下，满足上面2条及以上就可以选用MongoDBJava访问MongoDBmaven 依赖<dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>3.10.1</version> </dependency>文档添加MongoClient mongoClient = new MongoClient("192.168.211.133", 37017); MongoDatabase database = mongoClient.getDatabase("mg"); MongoCollection<Document> collection = database.getCollection("mg_preview"); Document document = Document.parse("{name:'lisi',city:'bj',birth_day:new ISODate('2001-08-01'),expectSalary:18000}"); collection.insertOne(document); mongoClient.close();文档查询MongoClient mongoClient = new MongoClient("192.168.211.133", 37017); MongoDatabase database = mongoClient.getDatabase("mg"); MongoCollection<Document> collection = database.getCollection("mg_preview"); Document sdoc = new Document(); //按expectSalary倒序 sdoc.append("expectSalary", -1); FindIterable<Document> findIterable = collection.find().sort(sdoc); for (Document document : findIterable) { System.out.println(document); } mongoClient.close();文档查询过滤MongoClient mongoClient = new MongoClient("192.168.211.133", 37017); MongoDatabase database = mongoClient.getDatabase("mg"); MongoCollection<Document> collection = database.getCollection("mg_preview"); Document sdoc=new Document(); //按expectSalary倒序 sdoc.append("expectSalary", -1); FindIterable<Document> findIterable = collection.find(Filters.gt("expectSalary",21000)).sort(sdoc); for (Document document : findIterable) { System.out.println(document); } mongoClient.close();Spring访问MongoDB基于maven新建工程，导入依赖的包<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>2.0.9.RELEASE</version> </dependency>在配置文件中配置 MongoTemplate<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd"> <mongo:db-factory id="mongoDbFactory" client-uri="mongodb://192.168.211.133:37017/mg"></mongo:db-factory> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg index="0" ref="mongoDbFactory"></constructor-arg> </bean> <context:component-scan base-package="com.mgtest"></context:component-scan> </beans>创建实体类，dao实现类注入 MongoTemplate 完成增删改查@Autowired private MongoTemplate mongoTemplate; @Override public void insertxxx(实体 xxx){ mongoTemplate.insert(xxx); } @Override public List<实体> findList(String name){ Query query = new Query(); query.addCriteria(Criteria.where("name").is(name)); List<实体> datas = mongoTemplate.find(query, 实体.class, "mg_datas"); return datas; }从Spring容器中获取dao对象进行测试 (注意:要开启组件扫描)注：实体类的id(_id)等同于MongoDB中的_id，如果set了值，那么MongoDB就不会产生_id，如果没有set值，那么MongoDB会自动生成一个_idClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); Dao dao = applicationContext.getBean(实体.class); //TODO 此处set值 dao.insertxxx(实体); dao.findList(name);Spring Boot 访问 MongoDBMongoTemplate 的方式基于maven新建springboot工程<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>2.2.2.RELEASE</version> </dependency>配置文件application.propertiesspring.data.mongodb.host=192.168.211.133 spring.data.mongodb.port=37017 spring.data.mongodb.database=mg创建实体类，dao实现类注入 MongoTemplate 完成增删改查@Autowired private MongoTemplate mongoTemplate; @Override public void insertxxx(实体 xxx){ mongoTemplate.insert(xxx); } @Override public List<实体> findList(String name){ Query query = new Query(); query.addCriteria(Criteria.where("name").is(name)); List<实体> datas = mongoTemplate.find(query, 实体.class, "mg_datas"); return datas; }从Spring容器中获取dao对象进行测试注：实体类的id(_id)等同于MongoDB中的_id，如果set了值，那么MongoDB就不会产生_id，如果没有set值，那么MongoDB会自动生成一个_id启动类main方法中：ApplicationContext applicationContext = SpringApplication.run(启动类.class, args); Dao dao = applicationContext.getBean(实体.class); //TODO 此处set值 dao.insertxxx(实体); dao.findList(name);MongoRepository 的方式基于maven新建springboot工程<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>2.2.2.RELEASE</version> </dependency>配置文件application.propertiesspring.data.mongodb.host=192.168.211.133 spring.data.mongodb.port=37017 spring.data.mongodb.database=mg编写实体类，并在实体类上打@Document(“集合名”)编写 Repository 接口，继承 MongoRepository方法具体参考:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation如果内置方法不够用，就自己定义如:定义find|read|get 等开头的方法进行查询从Spring容器中获取Repository对象进行测试
什么是索引索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构，它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录，可以根据目录中的页码快速找到所需的内容。索引目标是提高数据库的查询效率，没有索引的话，查询会进行全表扫描（scan every document in a collection），数据量大时严重降低了查询效率。默认情况下Mongo在一个集合（collection）创建时，自动地对集合的_id创建了唯一索引。索引类型单键索引 (Single Field)MongoDB支持所有数据类型中的单个字段索引，并且可以在文档的任何字段上定义。对于单个字段索引，索引键的排序顺序无关紧要，因为MongoDB可以在任一方向读取索引。单个例上创建索引：db.集合名.createIndex({"字段名":排序方式})特殊的单键索引 过期索引 TTL （ Time To Live）TTL索引是MongoDB中一种特殊的索引，可以支持文档在一定时间之后自动过期删除，目前TTL索引只能在单字段上建立，并且字段类型必须是日期类型。db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒数})复合索引(Compound Index）通常我们需要在多个字段的基础上搜索表/集合，这是非常频繁的。 如果是这种情况，我们可能会考虑在MongoDB中制作复合索引。 复合索引支持基于多个字段的索引，这扩展了索引的概念并将它们扩展到索引中的更大域。制作复合索引时要注意的重要事项包括：字段顺序与索引方向。db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )多键索引（Multikey indexes）针对属性包含数组数据的情况，MongoDB支持针对数组中每一个element创建索引，Multikey indexes支持strings，numbers和nested documentsdb.集合名.createIndex({"数组字段名":排序方式})地理空间索引（Geospatial Index）针对地理空间坐标数据创建索引。2dsphere索引，用于存储和查找球面上的点2d索引，用于存储和查找平面上的点db.company.insert( { loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] }, name: "大望路地铁", category : "Parks" } ) /*建立索引*/ db.company.ensureIndex( { loc : "2dsphere" } ) 参数不是1或-1，为2dsphere 或者 2d。还可以建立组合索引。 /*查找方圆大约5公里内的点*/ db.company.find({ "loc" : { "$geoWithin" : { "$center":[[116.482451,39.914176],0.05] } } })全文索引MongoDB提供了针对string内容的文本查询，Text Index支持任意属性值为string或string数组元素的索引查询。注意：一个集合仅支持最多一个Text Index，中文分词不理想，推荐ES。例如：键入有一个字段内容为 I like coffeedb.集合.createIndex({"字段": "text"}) /*可以查到字段包含coffee的文档*/ db.集合.find({"$text": {"$search": "coffee"}})哈希索引 Hashed Index针对属性的哈希值进行索引查询，当要使用Hashed index时，MongoDB能够自动的计算hash值，无需程序计算hash值。注：hash index仅支持等于查询，不支持范围查询。db.集合.createIndex({"字段": "hashed"})索引和explain分析索引管理创建索引并在后台运行db.COLLECTION_NAME.createIndex({"字段":排序方式}, {background: true});获取针对某个集合的索引db.COLLECTION_NAME.getIndexes()索引的大小db.COLLECTION_NAME.totalIndexSize()索引的重建db.COLLECTION_NAME.reIndex()索引的删除db.COLLECTION_NAME.dropIndex("INDEX-NAME") db.COLLECTION_NAME.dropIndexes() 注意: _id 对应的索引是删除不了的explain 分析使用js循环，插入100万条数据，不使用索引字段，查询查看执行计划，然后给某个字段建立索引，使用索引字段作为查询条件，再查看执行计划进行分析explain()也接收不同的参数，通过设置不同参数我们可以查看更详细的查询计划。queryPlanner：queryPlanner是默认参数，具体执行计划信息参考下面的表格。executionStats：executionStats会返回执行计划的一些统计信息(有些版本中和allPlansExecution等同)。allPlansExecution：allPlansExecution用来获取所有执行计划，结果参数基本与上文相同。queryPlanner 默认参数参数含义plannerVersion查询计划版本namespace要查询的集合（该值返回的是该query所查询的表）数据库.集合indexFilterSet针对该query是否有indexFilterparsedQuery查询条件winningPlan被选中的执行计划winningPlan.stage被选中执行计划的stage(查询方式)，常见的有：COLLSCAN/全表扫描：（应该知道就是CollectionScan，就是所谓的“集合扫描”，和mysql中table scan/heap scan类似，这个就是所谓的性能最烂最无奈的由来）、IXSCAN/索引扫描：（是IndexScan，这就说明我们已经命中索引了）、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询等winningPlan.inputStage用来描述子stage，并且为其父stage提供文档和索引关键字。winningPlan.stage的childstage如果此处是IXSCAN，表示进行的是index scanning。winningPlan.keyPattern所扫描的index内容winningPlan.indexNamewinning plan所选用的index。winningPlan.isMultiKey是否是Multikey，此处返回是false，如果索引建立在array上，此处将是true。winningPlan.direction此query的查询顺序，此处是forward，如果用了.sort({字段:-1})将显示backward。filter过滤条件winningPlan.indexBoundswinningplan所扫描的索引范围,如果没有制定范围就是[MaxKey,MinKey]，这主要是直接定位到mongodb的chunck中去查找数据，加快数据读取。rejectedPlans被拒绝的执行计划的详细返回，其中具体信息与winningPlan的返回中意义相同，故不在此赘述）serverInfoMongoDB服务器信息executionStats参数参数含义executionSuccess是否执行成功nReturned返回的文档数executionTimeMillis执行耗时totalKeysExamined索引扫描次数totalDocsExamined文档扫描次数executionStages这个分类下描述执行的状态stage扫描方式，具体可选值与上文的相同nReturned查询结果数量executionTimeMillisEstimate检索document获得数据的时间inputStage.executionTimeMillisEstimate该查询扫描文档 index所用时间works工作单元数，一个查询会分解成小的工作单元advanced优先返回的结果数docsExamined文档检查数目，与totalDocsExamined一致。检查了总共的document个数，而从返回上面的nReturned数量executionStats返回逐层分析第一层，executionTimeMillis最为直观explain返回值是executionTimeMillis值，指的是这条语句的执行时间，这个值当然是希望越少越好。其中有3个executionTimeMillis，分别是：executionStats.executionTimeMillis 该query的整体查询时间。executionStats.executionStages.executionTimeMillisEstimate 该查询检索document获得数据的时间。executionStats.executionStages.inputStage.executionTimeMillisEstimate 该查询扫描文档 index所用时间。第二层，index与document扫描数与查询返回条目数 这个主要讨论3个返回项 nReturned、totalKeysExamined、totalDocsExamined，分别代表该条查询返回的条目、索引扫描条目、文档扫描条目。 这些都是直观地影响到executionTimeMillis，我们需要扫描的越少速度越快。 对于一个查询，我们最理想的状态是：nReturned=totalKeysExamined=totalDocsExamined第三层，stage状态分析 那么又是什么影响到了totalKeysExamined和totalDocsExamined？是stage的类型。类型列举如下：COLLSCAN：全表扫描IXSCAN：索引扫描FETCH：根据索引去检索指定documentSHARD_MERGE：将各个分片返回数据进行mergeSORT：表明在内存中进行了排序LIMIT：使用limit限制返回数SKIP：使用skip进行跳过IDHACK：针对_id进行查询SHARDING_FILTER：通过mongos对分片数据进行查询COUNT：利用db.coll.explain().count()之类进行count运算TEXT：使用全文索引进行查询时候的stage返回PROJECTION：限定返回字段时候stage的返回对于普通查询，我希望看到stage的组合(查询的时候尽可能用上索引)：Fetch+IDHACKFetch+IXSCANLimit+（Fetch+IXSCAN）PROJECTION+IXSCANSHARDING_FITER+IXSCAN不希望看到包含如下的stage：COLLSCAN(全表扫描)SORT(使用sort但是无index)COUNT 不使用index进行count)allPlansExecution参数是queryPlanner参数和executionStats的拼接慢查询分析开启内置的查询分析器，记录读写操作效率db.setProfilingLevel(n,m)，n的取值可选0，1，20表示不记录1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值2表示记录所有的读写操作查询监控结果db.system.profile.find().sort({millis:-1}).limit(3)分析慢速查询应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等解读explain结果 确定是否缺少索引MongoDB 索引底层实现原理分析MongoDB 是文档型的数据库，它使用BSON 格式保存数据，比关系型数据库存储更方便。比如之前关系型数据库中处理用户、订单等数据要建立对应的表，还要建立它们之间的关联关系。但是BSON就不一样了，我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单，通俗易懂。MySql是关系型数据库，数据的关联性是非常强的，区间访问是常见的一种情况，底层索引组织数据使用B+树，B+树由于数据全部存储在叶子节点，并且通过指针串在一起，这样就很容易的进行区间遍历甚至全部遍历。MongoDB使用B-树，所有节点都有Data域，只要找到指定索引就可以进行访问，单次查询从结构上来看要快于MySql。B-树是一种自平衡的搜索树，形式很简单：B-树的特点:（1）多路，非二叉树（2）每个节点，既保存数据，又保存索引（3）搜索时，相当于二分查找B+树是B-树的变种：B+ 树的特点:（1）多路非二叉（2）只有叶子节点保存数据（3）搜索时 也相当于二分查找（4）增加了 相邻节点指针从上面我们可以看出最核心的区别主要有俩，一个是数据的保存位置，一个是相邻节点的指向。就是这俩造成了MongoDB和MySql的差别。（1）B+树相邻接点的指针可以大大增加区间访问性，可使用在范围查询等，而B-树每个节点 key 和 data 在一起 适合随机读写，而区间查找效率很差。（2）B+树更适合外部存储，也就是磁盘存储，使用B-结构的话，每次磁盘预读中的很多数据是用不上的数据。因此，它没能利用好磁盘预读的提供的数据。由于节点内无 data 域，每个节点能索引的范围更大更精确。（3）注意这个区别相当重要，是基于（1）（2）的，B-树每个节点即保存数据又保存索引树的深度小，所以磁盘IO的次数很少，B+树只有叶子节点保存，较B树而言深度大磁盘IO多，但是区间访问比较好。