小白必须懂的MongoDB的十大总结(下)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 小白必须懂的MongoDB的十大总结(下)

四、增删改查操作

1、添加文档

db.集合名.insert({k1:v1,k2:v2...}) :向当前数据库的该集合下添加文档

我们在添加文档的时候有如下注意点:

a) 文档就是键值对,数据类型是 BSON 格式,支持的值更加丰富。 BSONJSON 的扩展,新增了诸如日期,浮点等 JSON 不支持的数据类型。

b) 在添加的文档里面,都有一个 '_id' 的键,值为对象类型 ObjectID ,在这里,我们解释下 ObjectID 类型:

每个文档都有一个 _id 字段,并且同一集合中的 _id 值唯一,该字段可以是任意类型的数据,默认是一个 ObjectID 对象。

ObjectID 对象数据组成:时间戳|机器码|PID|计数器

_id 的键值我们可以自己输入,但是不能重复,但要注意的一点是在插入数据的时候,如果 _id 的值重复则会报错

c) 可以使用 js 代码来完成批量插入文档

example:

> for(var i=1;i<=10;i++){
... db.php.insert({'name':'xiaobai'+i,'age':i,'email':'xiaobai'+i+'@gmail.com'})
... }
WriteResult({ "nInserted" : 1 })
> db.php.find()
{ "_id" : ObjectId("5b931b74a39e4f4842ba36b3"), "name" : "xiaoming", "age" : 20, "email" : "xiaoming@gmail.com" }
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaohong", "age" : 18, "email" : "xiaohong@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36b5"), "name" : "xiaobai1", "age" : 1, "email" : "xiaobai1@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36b6"), "name" : "xiaobai2", "age" : 2, "email" : "xiaobai2@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36b7"), "name" : "xiaobai3", "age" : 3, "email" : "xiaobai3@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36b8"), "name" : "xiaobai4", "age" : 4, "email" : "xiaobai4@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36b9"), "name" : "xiaobai5", "age" : 5, "email" : "xiaobai5@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36ba"), "name" : "xiaobai6", "age" : 6, "email" : "xiaobai6@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bb"), "name" : "xiaobai7", "age" : 7, "email" : "xiaobai7@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bc"), "name" : "xiaobai8", "age" : 8, "email" : "xiaobai8@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bd"), "name" : "xiaobai9", "age" : 9, "email" : "xiaobai9@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36be"), "name" : "xiaobai10", "age" : 10, "email" : "xiaobai10@gmail.com" }
2、删除文档

db.集合名.remove{(条件)} :删除当前数据库下指定集合中满足条件的文档(不写条件则删除所有的文档)

example:

> db.php.remove({age:20})
WriteResult({ "nRemoved" : 1 })
> db.php.remove({age:{'$lt':6}})
WriteResult({ "nRemoved" : 5 })
> db.php.find()
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaohong", "age" : 18, "email" : "xiaohong@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36ba"), "name" : "xiaobai6", "age" : 6, "email" : "xiaobai6@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bb"), "name" : "xiaobai7", "age" : 7, "email" : "xiaobai7@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bc"), "name" : "xiaobai8", "age" : 8, "email" : "xiaobai8@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bd"), "name" : "xiaobai9", "age" : 9, "email" : "xiaobai9@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36be"), "name" : "xiaobai10", "age" : 10, "email" : "xiaobai10@gmail.com" }

这里我们在删除 php 集合中年龄小于6的文档时,我们使用了操作符来完成。

比较运算符

操作符 效果
$gt 大于
$lt 小于
$gte 大于等于
$lte 小于等于
$exists 存在与否
$in 包含
$ne 不等于
$nin 不包含

逻辑运算符

操作符 效果
$exists 存在与否
$or 或者
$and 并且
$not 不存在
$mod 求模
$where 位置

特别的 $exists: true 表示字段存在

排序 sort

操作 效果
$asc 升序
$desc 降序
3、更新文档

更新文档有两种方式进行修改

方法一、直接修改

db.集合名.update({条件},{新的文档}) :修改当前数据库下指定集合中满足条件的文档信息

example:

> db.php.find()
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaohong", "age" : 18, "email" : "xiaohong@gmail.com" }
> db.php.update({age:18},{name:'xiaobai5'})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.php.find()
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaobai5" }

db.集合.update(条件,新文档,是否新增,是否修改多条) :修改当前数据库下指定集合中满足条件的文档信息

  • 是否新增:如果值是1(true)则没有满足条件的 则添加
  • 是否修改多条:若值是1(true),如果满足条件的有多个文档 则都要修改

example:

> db.php.update({age:10},{name:'xiaoli'},true,true)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 9,
        "errmsg" : "multi update only works with $ operators"
    }
})

方法二、使用修改器

example:

我们要修改 age=6 的文档名称为 xiaosan ,并且其他键值不能丢失

我们可以使用修改器

  • $inc :加上一个数字
  • $set :修改某一个字段,如果该字段不存在就增这个字段

语法:db.集合名.update({条件},{修改器名称:{修改的键:修改的新值}})

> db.php.update({age:6},{'$set':{name:'xiaosan'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

那如果我们要修改 age=10 的文档的年龄增加十岁,我们可以这样做:

> db.php.update({age:10},{$inc:{age:10}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
4、查询文档

语法: db.集合名.find({条件})

example:

取出 php 集合里面的第一个文档

> db.php.findOne()
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaobai5" }

取出 php 集合里面 age=6 的文档

> db.php.find({age:6})
{ "_id" : ObjectId("5b931dfba39e4f4842ba36ba"), "name" : "xiaosan", "age" : 6, "email" : "xiaobai6@gmail.com" }

取出 php 集合里面 age>8 的文档

> db.php.find({age:{'$gt':8}})
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bd"), "name" : "xiaobai9", "age" : 9, "email" : "xiaobai9@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36be"), "name" : "xiaobai10", "age" : 20, "email" : "xiaobai10@gmail.com" }

取出 php 集合里面的文档,只显示 name

> db.php.find({},{age:1})//1表示只显示age键值
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4") }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36ba"), "age" : 6 }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bb"), "age" : 7 }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bc"), "age" : 8 }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bd"), "age" : 9 }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36be"), "age" : 20 }
> db.php.find({},{age:0})//1表示除了显示age键值,其他的都显示
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaobai5" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36ba"), "name" : "xiaosan", "email" : "xiaobai6@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bb"), "name" : "xiaobai7", "email" : "xiaobai7@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bc"), "name" : "xiaobai8", "email" : "xiaobai8@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bd"), "name" : "xiaobai9", "email" : "xiaobai9@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36be"), "name" : "xiaobai10", "email" : "xiaobai10@gmail.com" }

根据年龄的(降序|升序)来显示文档

db.集合名.find().sort({age:1})根据年龄升序
db.集合名.find().sort({age:0})根据年龄降序

显示 php 集合中的前三个文档

> db.php.find().limit(3)
{ "_id" : ObjectId("5b931b7ca39e4f4842ba36b4"), "name" : "xiaobai5" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36ba"), "name" : "xiaosan", "age" : 6, "email" : "xiaobai6@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bb"), "name" : "xiaobai7", "age" : 7, "email" : "xiaobai7@gmail.com" }

显示 php 集合中的第三个文档到第五个文档

> db.php.find().skip(2).limit(3)
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bb"), "name" : "xiaobai7", "age" : 7, "email" : "xiaobai7@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bc"), "name" : "xiaobai8", "age" : 8, "email" : "xiaobai8@gmail.com" }
{ "_id" : ObjectId("5b931dfba39e4f4842ba36bd"), "name" : "xiaobai9", "age" : 9, "email" : "xiaobai9@gmail.com" }

统计 php 集合中文档的个数

db.集合名.count():返回集合中有多少个文档

五、用户管理(权限控制)

1、权限概述

MongoDB 里面的用户是属于数据库的,每个数据库都有自己的管理员。管理员登录后,只能操作所属的数据库。注意:在 admin 的数据库中创建的用户是超级管理员,登陆后可以操作任何的数据库

2、创建用户

(1) 选择数据库

use 数据库的名称

(2) 添加用户

db.createUser(用户名,密码,是否只读)

第三个参数"是否只读"默认是 false ,创建的用户可以执行读写,如果是 true ,则创建的用户只能查询,不能修改。

注意点:在创建用户之前,必须先创建一个超级管理员

example:

> use admin
switched to db admin
> db.createUser({user:'user',
...  pwd:'passwd', 
...  roles:[
...    {role:'userAdminAnyDatabase', db:'admin'}
... ]
... })
Successfully added user: {
    "user" : "user",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}
3、验证权限(用户登录)

在添加完成管理员之后,我们做如下操作:

(1) 如果你是安装成windows服务的方式安装的,则卸载服务,在安装时添加一个 -auth 选项,auth 表示要开启权限认证

(2) 如果你是直接启动的方式,则停止服务,重新启动,在启动时也要添加 --auth 选项,auth 表示要开启权限认证

如果没有通过权限验证,直接操作数据库,则报如下错误提示:

error: {
       "$err" : "unauthorized db:test lock type:-1 client:127.0.0.1",
        "code" : 10057
}

如何通过权限验证

  1. 选择数据库
  2. 执行db.auth (用户名,密码)
4、删除用户和修改密码

注意:创建的用户名和密码是存储在各自数据库里面的 system.users 集合里面的。想要删除用户,则直接删除 system.users 集合里面的文档即可

5、总结说明

a) 非 admin 数据库的用户不能使用数据库命令,比如 show dbs

(b) admin 数据库中的用户被视为超级用户(即管理员),在认证之后,管理员可以读写所有数据库,执行特定的管理命令。

(c) 在开启安全检查之前,一定要至少有个管理员账户。

(d) 数据库的用户账号以文档的形式存储在 system.users 集合里面。可以在 system.users 集合中删除用户账号文档,就可以删除用户。

六、MongoDB中的索引

1、普通单列索引

我们用如下代码来测试:

for(var i=0;i<200000;i++){
    db.java.insert({name:'xiao'+i,age:i})
}

第一、我们先检验一下查询性能

var start=new Date()
db.java.find({name:'xiao156789'})
var end=new Date()
end-start
17510

第二、为 name 创建索引

> db.java.ensureIndex({name:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

第三、再执行第一部分代码可以看出有数量级的性能提升

语法:db.集合名.ensureIndex({键名:1}) :1是升序,-1是降序

2、多列索引(复合索引)

创建多列索引

语法:db.集合名.ensureIndex({field1:1/-1,field2:1/-1})

nameage 建立一个复合索引,可以使用 db.集合名.getIndexes() 查看创建的索引情况

3、子文档索引

语法: db.集合名.ensureIndex({field.subfield:1/-1})

如下文档可以建立子文档索引

{name:'诺基亚手机1',price:12.34,spc:{weight:100,area:'纽约'}}
{name:'诺基亚手机2',price:42.34,spc:{weight:200,area:'伦敦'}}

比如要查询 weight=100 的文档

db.goods.find({'spc.weight':100})

根据当前案例,我们建立子文档索引

db.net.ensureIndex({'spc.w':1})

4、唯一索引

语法: db.集合名.ensureIndex({name:-1},{unique:true})

5、查看索引

(1) 查看当前索引状态: db.集合名.getIndexes()

(2) 详情查看本次查询使用哪个索引和查询数据的状态信息: db.集合名.find({name:''xiao}).explain()

6、删除索引

删除单个索引: db.集合名.dropIndex({filed:1/-1})

删除所有索引: db.集合名.dropIndexes()

7、重建索引

一个表经过很多次修改后,导致表的文件产生空洞,索引文件也如此,可以通过索引的重建,减少索引文件碎片,并提高索引的效率,类似 mysql 中的 optimize table

mysql 里面使用 optimize table 语法: optimize table 表名

语法: db.集合名.reIndex()

8、索引使用注意事项

(1) 创建索引的时候,注意1是正序创建索引,-1是倒序创建索引

(2) 索引的创建在提高查询性能的同时会影响插入性能,对于经常查询少插入

(3) 复合索引要注意索引的先后顺序

(4) 每个键全建立索引不一定就能提高性能,索引不是万能的。

(5) 在做排序工作的时候如果是超大数据量也可以考虑加上索引用来提高排序的性能。

八、MongoDB中的数据导出与导出

利用mongoexport

  • -h host主机
  • -port 端口
  • -d 指明使用的库
  • -o 指明要导出的文件名
  • -csv 指定导出的csv格式
  • -q 过滤导出
  • -f field1 field2 列名
  • -u username 用户名
  • -p password 密码

注意:在使用用户名和密码是超级管理员的时候,如果端口是默认的可以不使用-port来指定端口

(2) 导入数据

  • -d 待导入的数据库
  • -c 待导入的集合(不存在会自己创建)
  • -type csv/json(默认)
  • -file 备份文件路径

例如:导入json

./bin/mongoimport -h -port 端口号 -d test -c goods -file ./goodsall.json

导入csv

./bin/mongoimport -h -port 端口号 -d test -c goods -type csv -f goods.id,goods.name -file ./goodsall.csv
./bin/mongoimport -h -port 端口号 -d test -c goods -type csv -f -headline -f goods.id,goods.name -file ./goodsall.csv

九、主从复制(读写分离)

主从复制是一个简单的数据库同步备份的集群技术,至少两台数据库服务器,可以分别设置主服务器和从服务器,对主服务器的任何操作都会同步到从服务器上。


img_28ccd39807c7d750c1f3ce72902c5876.png


img_5bf9dd8ba799e79c5a465fcd6bd30490.png


实现的注意点

1、在数据库集群中要明确的知道谁是主服务器,主服务器只有一台

2、从服务器要知道自己的数据源 也就是对应的主服务是谁

3、--master用来确定主服务器 --slave和--source来控制从服务器

配置步骤

(1) 启动主服务器

(2) 启动从服务器

(3) 客户端登录到主服务器

添加一些数据,测试是否同步到从服务器,在主服务器里面,添加了一些文档:

第一步,客户端登录到主服务器,添加一些文档

第二步,登录到从服务器,查看是否有数据,如果有数据,则成功了!

十、php操作MongoDB

1、安装扩展

注意:扩展文件,下载合适的php_mongodb.dll文件

1) php的版本

2) 是否是线程安全的thread safe(ts)

3) 是vc几的

4) php是32位的还是64位的

步骤

1) 把对应的扩展,拷贝到PHP的安装目录里面的ext目录下面,注意:拷贝后改名为php_mongo.dll,方便管理

2) 打开php.ini文件,引入该扩展

extension=php_mongo.dll

3) 重启Apache,使用phpinfo()函数测试

2、入门使用

1) 连接mongodb服务器

$m=new MongoClient("mongodb://root:root@localhost:8888/admin");
$db=$m->selectDb("stu");//选择数据库

2) 增删改查用法

增删改查

注意,在命令行里面的"." 变成了"->","{}"变成了数组

a) 添加一个文档

$db->php->insert(array('name'=>'李元霸','age'=>12));

b) 查询文档

$data=$db->php->find();

查询年龄等于9的文档:

$data=$db->php->find(array('age'=>9));

查询年龄大于9的文档:

//db.php.find({age:{'$gt':9}})
$data=$db->php->find(array('age'=>array('$gt':9)));

根据年龄降序显示:

$data=$db->php->find()->sort(array('age'=>1));
foreach($data as $v){
    echo $v['name'].'----'.$v['age'].'--'.$v['email'].'';
}

c) 修改文档,我们直接使用修改器来完成

把年龄等于8的名称改名为李白:

//db.php.update({age:8},{'$set':{'name':'李白'}})
$db->php->update(array('age'=>8),array('$set'=>array('name'=>'李白'));

d) 删除文档

比如删除年龄等于10的文档:

//db.php.remove({age:10})
$db->php->remove(array('age'=>10))
$data=$db->php->find()
foreach($data as $v){
    echo $v['name'].'----'.$v['age'].'--'.$v['email'].'';
}

3) 把mysql表里面的数据存储到mongodb里面

selectDb("stu");//选择数据库
//从mysql里面取出数据
$conn=mysql_connect('localhost','root','root');
mysql_query('use shop');
mysql_query('set names utf8');
$sql="select * from goods";
$res=mysql_query($sql);
while($row=mysql_fetch_assoc($res)){
    $db->goods->insert($row);
}
echo 'ok';
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
5月前
|
存储 NoSQL MongoDB
MongoDB实战面试指南:常见问题一网打尽
MongoDB实战面试指南:常见问题一网打尽
|
6月前
|
存储 NoSQL 大数据
【MongoDB 专栏】MongoDB 在大数据场景下的应用
【5月更文挑战第11天】MongoDB,适用于大数据时代,以其灵活数据模型、高可扩展性和快速性能在大数据场景中脱颖而出。它处理海量、多类型数据,支持高并发,并在数据分析、日志处理、内容管理和物联网应用中广泛应用。电商和互联网公司的案例展示了其在扩展性和业务适应性上的优势,但同时也面临数据一致性、资源管理、数据安全和性能优化的挑战。
668 1
【MongoDB 专栏】MongoDB 在大数据场景下的应用
|
6月前
|
存储 NoSQL MongoDB
【MongoDB 专栏】MongoDB 入门指南:从零开始学习
【5月更文挑战第10天】本文介绍了MongoDB,一个流行的NoSQL数据库,以其灵活的数据模型和高性能著称。内容包括MongoDB的基础知识、安装配置、文档数据模型、数据库操作(如创建、查询、更新和删除)、索引创建、数据备份恢复及性能优化策略。此外,还探讨了MongoDB在社交网络、电子商务等领域的应用。对于初学者,本文提供了从零开始学习MongoDB的入门指导。
106 0
【MongoDB 专栏】MongoDB 入门指南:从零开始学习
|
运维 NoSQL MongoDB
[慕课笔记]mongodb入门篇
[慕课笔记]mongodb入门篇
62 1
|
存储 JSON 缓存
小白必须懂的MongoDB的十大总结(上)
小白必须懂的MongoDB的十大总结(上)
668 0
小白必须懂的MongoDB的十大总结(上)
|
NoSQL 关系型数据库 MongoDB
《《玩转MongoDB 从入门到实战》》下载电子版
阿里云开发者数据团队推出了《玩转MongoDB从入门到实战》以理论+案例全方位解析,内含复制集、分片集群、ChangeStreams和事务功能的使用及原理介绍,帮助更多开发者快速掌握MongoDB核心架构及特色功能。
115 0
《《玩转MongoDB 从入门到实战》》下载电子版
|
存储 SQL JSON
走进 MongoDB|学习笔记
快速学习走进 MongoDB
232 0
走进 MongoDB|学习笔记
|
设计模式 机器学习/深度学习 JSON
『MongoDB』MongoDB模型设计的成神之路
📣读完这篇文章里你能收获到 - 为什么很多人认为MongoDB是无模式? - 文档模型跟传统的关系模型有什么区别? - 关于MongoDB的模型设计模式,你知道几个? - MongoDB如何进行表关联? - 文档模型的设计规范及设计原则 - 文档建模模型设计三部曲 - 1-1关系建模,1-N关系建模,N-N关系建模的建议 - 针对不同的场景提供丰富的设计案例分享
461 0
『MongoDB』MongoDB模型设计的成神之路
|
JSON NoSQL 安全
走进 MongoDB
了解MongoDB相关概念和基本操作。
走进 MongoDB
|
存储 NoSQL 关系型数据库
带你快速掌握MongoDB核心技术,《玩转MongoDB从入门到实战》开放下载!
作为MongoDB官方的全球战略合作伙伴,阿里云已经全网独家上线MongoDB 4.4版本。我们推出了《玩转MongoDB从入门到实战》以帮助更多开发者快速掌握MongoDB核心架构及特色功能。
21424 0
带你快速掌握MongoDB核心技术,《玩转MongoDB从入门到实战》开放下载!