├── README.md ├── aggregation ├── dump.tar.gz └── lab-script.md ├── geektime-mongodb-course-chapter-1.pdf ├── geektime-mongodb-course-chapter-2.pdf ├── geektime-mongodb-course-chapter-3.pdf ├── geektime-mongodb-course-chapter-4.pdf ├── helloworld ├── hello_world.py └── lab-script.md ├── replicaset └── lab-script.md ├── sharded-cluster └── lab-script.md ├── spark-demo ├── SparkConnectorDemo.iml ├── pom.xml └── src │ ├── .DS_Store │ ├── main │ ├── .DS_Store │ ├── java │ │ ├── .DS_Store │ │ └── org │ │ │ ├── .DS_Store │ │ │ └── geekbang │ │ │ ├── .DS_Store │ │ │ └── time │ │ │ ├── .DS_Store │ │ │ └── spark │ │ │ ├── Config.java │ │ │ ├── MongoBase.java │ │ │ └── SparkDemo.java │ └── resources │ │ └── config.properties │ └── test │ └── .DS_Store └── transaction ├── read-isolation.md ├── write-conflict.md └── write-wait.md /README.md: -------------------------------------------------------------------------------- 1 | Files for MongoDB Course on Geektime 2 | 3 | -------------------------------------------------------------------------------- /aggregation/dump.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/aggregation/dump.tar.gz -------------------------------------------------------------------------------- /aggregation/lab-script.md: -------------------------------------------------------------------------------- 1 | # 实验:聚合框架 2 | 3 | ## 简介 4 | 5 | 测试数据中模拟了2019年1月1日~2019年10月31日之间的订单和订单行数据,总计`100000`条。这些数据中包括以下主要字段: 6 | 7 | - `userId`: 下单人ID; 8 | - `name`: 订单人联系姓名; 9 | - `orderDate`: 下单日期; 10 | - `shippingFee`: 运费; 11 | - `total`: 订单物品总金额(不包括运费); 12 | - `status`: 订单状态,取值包括`["created", "cancelled", "shipping", "fulfilled", "completed"]` 13 | - `orderLines`: 订单包含的物品; 14 | - `price`: 物品售价; 15 | - `cost`: 物品成本; 16 | - `qty`: 购买件数; 17 | - `sku`: 产品唯一编号; 18 | 19 | ## 准备工作 20 | 21 | ### 导入实验数据 22 | 23 | ```bash 24 | # 解压实验数据文件 25 | tar -zxvf dump.tar.gz 26 | # 将实验数据导入到MongoDB 27 | mongorestore -h 127.0.0.1:27017 dump 28 | ``` 29 | 30 | ### 验证导入结果 31 | 32 | ```javascript 33 | use mock 34 | db.orders.count() 35 | // 执行结果:100000 36 | db.orders.findOne() 37 | // 执行结果:单条数据示例 38 | ``` 39 | 40 | ## 实验内容 41 | 42 | ### 实验一:总销量 43 | 44 | 计算到目前为止的总销售额 45 | 46 | - 无论订单状态 47 | - 不限制时间范围 48 | - 不算运费 49 | 50 | ```javascript 51 | db.orders.aggregate([ 52 | { 53 | $group: { 54 | _id: null, 55 | total: { 56 | $sum: "$total" 57 | } 58 | } 59 | } 60 | ]) 61 | // 结果: 62 | // { "_id" : null, "total" : NumberDecimal("44019609") } 63 | ``` 64 | 65 | ### 实验二:订单金额汇总 66 | 67 | 查询2019年第一季度(1月1日~3月31日)订单中已完成(completed)状态的总金额和总数量: 68 | 69 | ```javascript 70 | db.orders.aggregate([ 71 | { 72 | // 步骤1:匹配条件 73 | $match: { 74 | status: "completed", 75 | orderDate: { 76 | $gte: ISODate("2019-01-01"), 77 | $lt: ISODate("2019-04-01") 78 | } 79 | } 80 | }, { 81 | $group: { 82 | // 步骤二:聚合订单总金额、总运费、总数量 83 | _id: null, 84 | total: { 85 | $sum: "$total" 86 | }, 87 | shippingFee: { 88 | $sum: "$shippingFee" 89 | }, 90 | count: { 91 | $sum: 1 92 | } 93 | } 94 | }, { 95 | $project: { 96 | // 计算总金额 97 | grandTotal: { 98 | $add: ["$total", "$shippingFee"] 99 | }, 100 | count: 1, 101 | _id: 0 102 | } 103 | } 104 | ]) 105 | // 结果: 106 | // { "count" : 5875, "grandTotal" : NumberDecimal("2636376.00") } 107 | ``` 108 | 109 | ### 实验三:计算月销量 110 | 111 | 计算前半年每个月的销售额和总订单数。 112 | 113 | - 不算运费 114 | - 不算取消(cancelled)状态的订单 115 | 116 | ```javascript 117 | db.orders.aggregate([ 118 | { 119 | // 步骤1:匹配条件 120 | $match: { 121 | status: { 122 | $ne: "cancelled" 123 | }, 124 | orderDate: { 125 | $gte: ISODate("2019-01-01"), 126 | $lt: ISODate("2019-07-01") 127 | } 128 | } 129 | }, { 130 | // 步骤2:取出年月 131 | $project: { 132 | month: { 133 | $dateToString: { 134 | date: "$orderDate", 135 | format: "%G年%m月" 136 | } 137 | }, 138 | total: 1 139 | } 140 | }, { 141 | // 步骤3:按年月分组汇总 142 | $group: { 143 | _id: "$month", 144 | total: { 145 | $sum: "$total" 146 | }, 147 | count: { 148 | $sum: 1 149 | } 150 | } 151 | } 152 | ]) 153 | // 结果: 154 | // { "_id" : "2019年01月", "total" : NumberDecimal("3620936"), "count" : 8249 } 155 | // { "_id" : "2019年04月", "total" : NumberDecimal("3551291"), "count" : 8038 } 156 | // { "_id" : "2019年06月", "total" : NumberDecimal("3496645"), "count" : 7942 } 157 | // { "_id" : "2019年05月", "total" : NumberDecimal("3590503"), "count" : 8163 } 158 | // { "_id" : "2019年02月", "total" : NumberDecimal("3258201"), "count" : 7387 } 159 | // { "_id" : "2019年03月", "total" : NumberDecimal("3574185"), "count" : 8167 } 160 | ``` 161 | 162 | ### 实验四:地区销量top1 163 | 164 | 计算第一季度每个州(state)销量最多的`sku`第一名。 165 | 166 | - 只算`complete`订单; 167 | 168 | ```javascript 169 | db.orders.aggregate([ 170 | { 171 | // 步骤1:匹配条件 172 | $match: { 173 | status: "completed", 174 | orderDate: { 175 | $gte: ISODate("2019-01-01"), 176 | $lt: ISODate("2019-04-01") 177 | } 178 | } 179 | }, { 180 | // 步骤2:按订单行展开 181 | $unwind: "$orderLines" 182 | }, { 183 | // 步骤3:按sku汇总 184 | $group: { 185 | _id: { 186 | state: "$state", 187 | sku: "$orderLines.sku" 188 | }, 189 | count: { 190 | $sum: "$orderLines.qty" 191 | } 192 | } 193 | }, { 194 | // 步骤4:按州和销量排序 195 | $sort: { 196 | "_id.state": 1, 197 | "count": -1 198 | } 199 | }, { 200 | // 步骤4:取每个州top1 201 | $group: { 202 | _id: "$_id.state", 203 | sku: { 204 | $first: "$_id.sku" 205 | }, 206 | count: { 207 | $first: "$count" 208 | } 209 | } 210 | } 211 | ]) 212 | // 结果: 213 | // { "_id" : "Wyoming", "sku" : "8181", "count" : 183 } 214 | // { "_id" : "Wisconsin", "sku" : "9684", "count" : 195 } 215 | // { "_id" : "West Virginia", "sku" : "9376", "count" : 170 } 216 | // { "_id" : "North Dakota", "sku" : "2411", "count" : 243 } 217 | // ... 218 | ``` 219 | 220 | ### 实验五:统计SKU销售件数 221 | 222 | 统计每个`sku`在第一季度销售的次数。 223 | 224 | - 不算取消(cancelled)状态的订单; 225 | - 按销售数量降序排列; 226 | 227 | ```javascript 228 | db.orders.aggregate([ 229 | { 230 | // 步骤1:匹配条件 231 | $match: { 232 | status: { 233 | $ne: "cancelled" 234 | }, 235 | orderDate: { 236 | $gte: ISODate("2019-01-01"), 237 | $lt: ISODate("2019-04-01") 238 | } 239 | } 240 | }, { 241 | // 步骤2:按订单行展开 242 | $unwind: "$orderLines" 243 | }, { 244 | // 步骤3:按sku汇总 245 | $group: { 246 | _id: "$orderLines.sku", 247 | count: { 248 | $sum: "$orderLines.qty" 249 | } 250 | } 251 | }, { 252 | $sort: { 253 | count: -1 254 | } 255 | } 256 | ]) 257 | // 结果: 258 | // { "_id" : "4751", "count" : 2115 } 259 | // { "_id" : "798", "count" : 1945 } 260 | // { "_id" : "3863", "count" : 1913 } 261 | // { "_id" : "2558", "count" : 1896 } 262 | // ... 263 | ``` 264 | -------------------------------------------------------------------------------- /geektime-mongodb-course-chapter-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/geektime-mongodb-course-chapter-1.pdf -------------------------------------------------------------------------------- /geektime-mongodb-course-chapter-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/geektime-mongodb-course-chapter-2.pdf -------------------------------------------------------------------------------- /geektime-mongodb-course-chapter-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/geektime-mongodb-course-chapter-3.pdf -------------------------------------------------------------------------------- /geektime-mongodb-course-chapter-4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/geektime-mongodb-course-chapter-4.pdf -------------------------------------------------------------------------------- /helloworld/hello_world.py: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | from pprint import pprint 3 | 4 | uri = "mongodb://127.0.0.1:27017/" 5 | client = MongoClient(uri) 6 | pprint(client) 7 | 8 | 9 | db = client["eshop"] 10 | user_coll = db["users"] 11 | 12 | new_user = { 13 | "username": "nina", 14 | "password": "xxxx", 15 | "email": "123456@qq.com" 16 | } 17 | result = user_coll.insert_one(new_user) 18 | pprint(result) 19 | 20 | result = user_coll.find_one() 21 | pprint(result) 22 | 23 | result = user_coll.update_one({ 24 | "username": "nina" 25 | }, { 26 | "$set": { 27 | "phone": "123456789" 28 | } 29 | }) 30 | 31 | result = user_coll.find_one({ "username": "nina" }) 32 | pprint(result) 33 | 34 | 35 | client.close() 36 | -------------------------------------------------------------------------------- /helloworld/lab-script.md: -------------------------------------------------------------------------------- 1 | # Hellow World 2 | 3 | ## 准备工作 4 | 5 | ### 创建mongod实例 6 | 7 | 为了在实验中使用MongoDB,我们先启动一个临时mongod实例: 8 | 9 | ```bash 10 | mongod --dbpath /data --port 27017 11 | ``` 12 | 13 | 以上指令将使用`/data`作为数据目录(如果不存在请创建),在默认端口`27017`启动一个临时mongod实例。 14 | 15 | ### 安装MongoDB驱动 16 | 17 | 在使用MongoDB之前必须先安装用于访问数据库的驱动程序。这里我们以Python为例给大家演示: 18 | 19 | ```bash 20 | pip install pymongo 21 | ``` 22 | 23 | ### 检查驱动 24 | 25 | 在python交互模式下导入pymongo,检查驱动是否已正确安装: 26 | 27 | ```python 28 | import pymongo 29 | pymongo.version 30 | # 结果: 31 | # '3.7.2' 32 | ``` 33 | 34 | ## 使用驱动连接MongoDB 35 | 36 | 使用驱动连接到MongoDB集群只需要指定MongoDB连接字符串即可。其基本格式可以参考文档: [Connection String URI Format](https://docs.mongodb.com/manual/reference/connection-string/) 37 | 38 | 连接字符串的大部分参数在不同编程语言之间是通用的。本实验中,我们使用以下连接字符串: 39 | 40 | ```python 41 | uri = "mongodb://127.0.0.1:27017/?minPoolSize=10&maxPoolSize=100" 42 | ``` 43 | 44 | 这里指定了连接池保持连接的最小数量是10,最大连接数100。 45 | 46 | 要连接到MongoDB,在Python交互模式下执行以下语句即可: 47 | 48 | ```python 49 | from pymongo import MongoClient 50 | 51 | uri = "mongodb://127.0.0.1:27017/?minPoolSize=10&maxPoolSize=100" 52 | client = MongoClient(uri) 53 | print client 54 | # 结果: 55 | # MongoClient(host=['127.0.0.1:27017'], document_class=dict, tz_aware=False, connect=True, minpoolsize=10, maxpoolsize=100) 56 | ``` 57 | 58 | ## 执行CRUD操作 59 | 60 | 在上述过程中创建`MongoClient`后,我们将使用它来完成CRUD操作。假设我们将使用`foo`数据库的`bar`集合来完成测试: 61 | 62 | ```python 63 | test_db = client["foo"] 64 | bar_coll = test_db["bar"] 65 | result = bar_coll.insert_one({ 66 | "string": "Hello World" 67 | }) 68 | print result 69 | # 结果: 70 | # 71 | ``` 72 | 73 | 我们将结果查询出来看看是否正确: 74 | 75 | ```python 76 | result = bar_coll.find_one({ 77 | "string": "Hello World" 78 | }) 79 | print result 80 | # 结果: 81 | # {u'_id': ObjectId('5dbeb2290f08fbb017e0e583'), u'string': u'Hello World'} 82 | ``` 83 | 84 | 我们可以注意到这个对象上添加了一个`_id`,它是MongoDB对每个对象赋予的唯一主键,如果没有指定则由系统自动分配一个`ObjectId`来填充。 85 | 86 | 现在我们尝试对刚才的文档进行一点修改,然后再查询它: 87 | 88 | ```python 89 | result = bar_coll.update_one({ 90 | "string": "Hello World" 91 | }, { 92 | "$set": { 93 | "from": "Tom the cat" 94 | } 95 | }) 96 | result = bar_coll.find_one({ 97 | "string": "Hello World" 98 | }) 99 | print result 100 | # 结果: 101 | # {u'_id': ObjectId('5dbeb2290f08fbb017e0e583'), u'from': u'Tom the cat', u'string': u'Hello World'} 102 | ``` 103 | 104 | 最后我们将它从表中删除: 105 | 106 | ```python 107 | result = bar_coll.delete_one({ 108 | "string": "Hello World" 109 | }) 110 | print result 111 | # 结果: 112 | # 113 | ``` 114 | 115 | ## 非交互模式下执行 116 | 117 | 除了在Python交互模式下执行,我们当然也可以将代码放在一个文件中执行。把等价的代码放到文件中: 118 | 119 | ```python 120 | # hello_world.py 121 | from pymongo import MongoClient 122 | 123 | uri = "mongodb://127.0.0.1:27017/?minPoolSize=10&maxPoolSize=100" 124 | client = MongoClient(uri) 125 | print client 126 | 127 | test_db = client["foo"] 128 | bar_coll = test_db["bar"] 129 | result = bar_coll.insert_one({ 130 | "string": "Hello World" 131 | }) 132 | print result 133 | 134 | result = bar_coll.find_one({ 135 | "string": "Hello World" 136 | }) 137 | print result 138 | 139 | result = bar_coll.update_one({ 140 | "string": "Hello World" 141 | }, { 142 | "$set": { 143 | "from": "Tom the cat" 144 | } 145 | }) 146 | result = bar_coll.find_one({ 147 | "string": "Hello World" 148 | }) 149 | print result 150 | 151 | result = bar_coll.delete_one({ 152 | "string": "Hello World" 153 | }) 154 | print result 155 | 156 | client.close() 157 | ``` 158 | 159 | 执行这个文件: 160 | 161 | ```bash 162 | python hello_world.py 163 | ``` 164 | 165 | 我们将得到跟之前相同的结果。 166 | -------------------------------------------------------------------------------- /replicaset/lab-script.md: -------------------------------------------------------------------------------- 1 | # 实验:搭建复制集 2 | 3 | ## 准备工作 4 | 5 | 安装步骤请参考“1.4 实验:安装Mongo”。 6 | 7 | - Windows系统请事先配置好环境变量以方便启动`mongod`进程; 8 | - Linux和Mac系统请配置`PATH`变量; 9 | 10 | ## 搭建复制集 11 | 12 | ### 创建数据目录 13 | 14 | MongoDB启动时将使用一个数据目录存放所有数据文件。我们将为3个复制集节点创建各自的数据目录。 15 | 16 | Linux/MacOS: 17 | 18 | ```bash 19 | mkdir -p /data{1,2,3} 20 | ``` 21 | 22 | Windows: 23 | 24 | ```cmd 25 | md c:\data1 26 | md c:\data2 27 | md c:\data3 28 | ``` 29 | 30 | ### 准备配置文件 31 | 32 | 正常情况下复制集的每个`mongod`进程应该位于不同的服务器。我们现在在一台机器上运行3个进程,因此要为它们各自配置: 33 | 34 | - 不同的端口。示例中将使用28017/28018/28019. 35 | - 不同的数据目录。示例中将使用: 36 | - `/data1`或`c:\data1` 37 | - `/data2`或`c:\data2` 38 | - `/data3`或`c:\data3` 39 | - 不同日志文件路径。示例中将使用: 40 | - `/data1/mongod.log`或`c:\data1\mongod.log` 41 | - `/data2/mongod.log`或`c:\data2\mongod.log` 42 | - `/data3/mongod.log`或`c:\data3\mongod.log` 43 | 44 | 这些配置文件标准格式如下,请修改必要的参数完成3个实例各自的配置文件: 45 | 46 | Linux/Mac: 47 | 48 | ```yaml 49 | # /data1/mongod.conf 50 | systemLog: 51 | destination: file 52 | path: /data1/mongod.log # 日志文件路径 53 | logAppend: true 54 | storage: 55 | dbPath: /data1 # 数据目录 56 | net: 57 | bindIp: 0.0.0.0 58 | port: 28017 # 端口 59 | replication: 60 | replSetName: rs0 61 | processManagement: 62 | fork: true 63 | ``` 64 | 65 | Windows: 66 | 67 | ```yaml 68 | # c:\data1\mongod.conf 69 | systemLog: 70 | destination: file 71 | path: c:\data1\mongod.log # 日志文件路径 72 | logAppend: true 73 | storage: 74 | dbPath: c:\data1 # 数据目录 75 | net: 76 | bindIp: 0.0.0.0 77 | port: 28017 # 端口 78 | replication: 79 | replSetName: rs0 80 | ``` 81 | 82 | ### 执行进程 83 | 84 | Linux/Mac: 85 | 86 | ```bash 87 | mongod -f /data1/mongod.conf 88 | mongod -f /data2/mongod.conf 89 | mongod -f /data3/mongod.conf 90 | ``` 91 | 92 | 注意:如果启用了SELinux,可能阻止上述进程启动。简单起见请关闭SELinux。 93 | 94 | Windows: 95 | 96 | ```cmd 97 | mongod -f c:\data1\mongod.conf 98 | mongod -f c:\data2\mongod.conf 99 | mongod -f c:\data3\mongod.conf 100 | ``` 101 | 102 | 因为Windows不支持fork,以上命令需要在3个不同的窗口执行,执行后不可关闭窗口否则进程将直接结束。 103 | 104 | ### 配置复制集 105 | 106 | 进入mongo shell: 107 | 108 | ```bash 109 | mongo --port 28017 110 | ``` 111 | 112 | 创建复制集: 113 | 114 | ```javascript 115 | rs.initiate({ 116 | _id: "rs0", 117 | members: [{ 118 | _id: 0, 119 | host: "localhost:28017" 120 | },{ 121 | _id: 1, 122 | host: "localhost:28018" 123 | },{ 124 | _id: 2, 125 | host: "localhost:28019" 126 | }] 127 | }) 128 | ``` 129 | 130 | 查看复制集状态: 131 | 132 | ```javascript 133 | rs.status() 134 | // 输出信息 135 | { 136 | "set" : "rs0", 137 | "date" : ISODate("2019-11-03T09:27:49.555Z"), 138 | "myState" : 1, 139 | "term" : NumberLong(1), 140 | "syncingTo" : "", 141 | "syncSourceHost" : "", 142 | "syncSourceId" : -1, 143 | "heartbeatIntervalMillis" : NumberLong(2000), 144 | "optimes" : { 145 | "lastCommittedOpTime" : { 146 | "ts" : Timestamp(1572773266, 1), 147 | "t" : NumberLong(1) 148 | }, 149 | "appliedOpTime" : { 150 | "ts" : Timestamp(1572773266, 1), 151 | "t" : NumberLong(1) 152 | }, 153 | "durableOpTime" : { 154 | "ts" : Timestamp(1572773266, 1), 155 | "t" : NumberLong(1) 156 | } 157 | }, 158 | "members" : [ 159 | { 160 | "_id" : 0, 161 | "name" : "localhost:28017", 162 | "health" : 1, 163 | "state" : 1, 164 | "stateStr" : "PRIMARY", 165 | "uptime" : 208, 166 | "optime" : { 167 | "ts" : Timestamp(1572773266, 1), 168 | "t" : NumberLong(1) 169 | }, 170 | "optimeDate" : ISODate("2019-11-03T09:27:46Z"), 171 | "syncingTo" : "", 172 | "syncSourceHost" : "", 173 | "syncSourceId" : -1, 174 | "infoMessage" : "could not find member to sync from", 175 | "electionTime" : Timestamp(1572773214, 1), 176 | "electionDate" : ISODate("2019-11-03T09:26:54Z"), 177 | "configVersion" : 1, 178 | "self" : true, 179 | "lastHeartbeatMessage" : "" 180 | }, 181 | { 182 | "_id" : 1, 183 | "name" : "localhost:28018", 184 | "health" : 1, 185 | "state" : 2, 186 | "stateStr" : "SECONDARY", 187 | "uptime" : 66, 188 | "optime" : { 189 | "ts" : Timestamp(1572773266, 1), 190 | "t" : NumberLong(1) 191 | }, 192 | "optimeDurable" : { 193 | "ts" : Timestamp(1572773266, 1), 194 | "t" : NumberLong(1) 195 | }, 196 | "optimeDate" : ISODate("2019-11-03T09:27:46Z"), 197 | "optimeDurableDate" : ISODate("2019-11-03T09:27:46Z"), 198 | "lastHeartbeat" : ISODate("2019-11-03T09:27:48.410Z"), 199 | "lastHeartbeatRecv" : ISODate("2019-11-03T09:27:47.875Z"), 200 | "pingMs" : NumberLong(0), 201 | "lastHeartbeatMessage" : "", 202 | "syncingTo" : "localhost:28017", 203 | "syncSourceHost" : "localhost:28017", 204 | "syncSourceId" : 0, 205 | "infoMessage" : "", 206 | "configVersion" : 1 207 | }, 208 | { 209 | "_id" : 2, 210 | "name" : "localhost:28019", 211 | "health" : 1, 212 | "state" : 2, 213 | "stateStr" : "SECONDARY", 214 | "uptime" : 66, 215 | "optime" : { 216 | "ts" : Timestamp(1572773266, 1), 217 | "t" : NumberLong(1) 218 | }, 219 | "optimeDurable" : { 220 | "ts" : Timestamp(1572773266, 1), 221 | "t" : NumberLong(1) 222 | }, 223 | "optimeDate" : ISODate("2019-11-03T09:27:46Z"), 224 | "optimeDurableDate" : ISODate("2019-11-03T09:27:46Z"), 225 | "lastHeartbeat" : ISODate("2019-11-03T09:27:48.410Z"), 226 | "lastHeartbeatRecv" : ISODate("2019-11-03T09:27:47.929Z"), 227 | "pingMs" : NumberLong(0), 228 | "lastHeartbeatMessage" : "", 229 | "syncingTo" : "localhost:28018", 230 | "syncSourceHost" : "localhost:28018", 231 | "syncSourceId" : 1, 232 | "infoMessage" : "", 233 | "configVersion" : 1 234 | } 235 | ], 236 | "ok" : 1 237 | } 238 | ``` 239 | 240 | 简单使用: 241 | 242 | ```javascript 243 | show dbs 244 | // 结果 245 | admin 0.000GB 246 | local 0.000GB 247 | 248 | use local 249 | show tables 250 | // 结果 251 | me 252 | oplog.rs 253 | replset.election 254 | replset.minvalid 255 | startup_log 256 | system.replset 257 | ``` 258 | 259 | ### 调整复制集配置 260 | 261 | ```javascript 262 | var conf = rs.conf() 263 | // 将0号节点的优先级调整为10 264 | conf.members[0].priority = 10; 265 | // 将1号节点调整为hidden节点 266 | conf.members[1].hidden = true; 267 | // hidden节点必须配置{priority: 0} 268 | conf.members[1].priority = 0; 269 | // 应用以上调整 270 | rs.reconfig(conf); 271 | ``` 272 | -------------------------------------------------------------------------------- /sharded-cluster/lab-script.md: -------------------------------------------------------------------------------- 1 | # 实验:分片集群搭建及扩容 2 | 3 | ## 准备工作 4 | 5 | ### 为服务器设置域名 6 | 7 | 我们将使用域名来搭建分片集。使用域名也是搭建MongoDB集群的推荐方式,它在以后发生迁移时将带来很多便利。 8 | 9 | ```bash 10 | echo "54.238.247.149 geekdemo1 member1.example.com member2.example.com" >> /etc/hosts 11 | echo "54.178.197.163 geekdemo2 member3.example.com member4.example.com" >> /etc/hosts 12 | echo "13.230.147.181 geekdemo3 member5.example.com member6.example.com" >> /etc/hosts 13 | ``` 14 | ### 服务器分工 15 | 16 | 在本例中,我们将使用: 17 | 18 | - `member1`/`member3`/`member5`搭建`shard1`和`config` 19 | - `member2`/`member4`/`member6`搭建`shard2`和`mongos` 20 | 21 | ||member1|member2|member3|member4|member5|member6| 22 | |-----|-----|-----|-----|-----|-----|-----| 23 | |shard1|✓||✓||✓|| 24 | |shard2||✓||✓||✓| 25 | |config|✓||✓||✓|| 26 | |mongos||✓||✓||✓| 27 | 28 | ### 准备分片目录 29 | 30 | 在各服务器上创建数据目录,我们使用`/data`,请按自己需要修改为其他目录: 31 | 32 | - 在`member1`/`member3`/`member5`上执行以下命令: 33 | 34 | ```bash 35 | mkdir -p /data/shard1/ 36 | mkdir -p /data/config/ 37 | ``` 38 | 39 | - 在`member2`/`member4`/`member6`上执行以下命令: 40 | 41 | ```bash 42 | mkdir -p /data/shard2/ 43 | mkdir -p /data/mongos/ 44 | ``` 45 | 46 | ## 搭建分片 47 | 48 | ### 搭建shard1 49 | 50 | 在`member1`/`member3`/`member5`上执行以下命令。注意以下参数: 51 | 52 | - `shardsvr`: 表示这不是一个普通的复制集,而是分片集的一部分; 53 | - `wiredTigerCacheSizeGB`: 该参数表示MongoDB能够使用的缓存大小。默认值为`(RAM - 1GB) / 2`。 54 | - 不建议配置超过默认值,有OOM的风险; 55 | - 因为我们当前测试会在一台服务器上运行多个实例,因此配置了较小的值; 56 | - `bind_ip`: 生产环境中强烈建议不要绑定外网IP,此处为了方便演示绑定了所有IP地址。类似的道理,生产环境中应开启认证`--auth`,此处为演示方便并未使用; 57 | 58 | ```bash 59 | mongod --bind_ip 0.0.0.0 --replSet shard1 --dbpath /data/shard1 --logpath /data/shard1/mongod.log --port 27010 --fork --shardsvr --wiredTigerCacheSizeGB 1 60 | ``` 61 | 62 | 用这三个实例搭建shard1复制集: 63 | 64 | - 任意连接到一个实例,例如我们连接到`member1.example.com`: 65 | 66 | ```bash 67 | mongo --host member1.example.com:27010 68 | ``` 69 | 70 | - 初始化`shard1`复制集。我们使用如下配置初始化复制集: 71 | 72 | ```javascript 73 | rs.initiate({ 74 | _id: "shard1", 75 | "members" : [ 76 | { 77 | "_id": 0, 78 | "host" : "member1.example.com:27010" 79 | }, 80 | { 81 | "_id": 1, 82 | "host" : "member3.example.com:27010" 83 | }, 84 | { 85 | "_id": 2, 86 | "host" : "member5.example.com:27010" 87 | } 88 | ] 89 | }); 90 | ``` 91 | 92 | ### 搭建config 93 | 94 | 与`shard1`类似的方式,我们可以搭建`config`服务器。在`member1`/`member3`/`member5`上执行以下命令: 95 | 96 | - 运行`config`实例: 97 | 98 | ```bash 99 | mongod --bind_ip 0.0.0.0 --replSet config --dbpath /data/config --logpath /data/config/mongod.log --port 27019 --fork --configsvr --wiredTigerCacheSizeGB 1 100 | ``` 101 | 102 | - 连接到`member1`: 103 | 104 | ```bash 105 | mongo --host member1.example.com:27019 106 | ``` 107 | 108 | - 初始化`config`复制集: 109 | ```javascript 110 | rs.initiate({ 111 | _id: "config", 112 | "members" : [ 113 | { 114 | "_id": 0, 115 | "host" : "member1.example.com:27019" 116 | }, 117 | { 118 | "_id": 1, 119 | "host" : "member3.example.com:27019" 120 | }, 121 | { 122 | "_id": 2, 123 | "host" : "member5.example.com:27019" 124 | } 125 | ] 126 | }); 127 | ``` 128 | 129 | ### 搭建mongos 130 | 131 | mongos的搭建比较简单,我们在`member2`/`member4`/`member6`上搭建3个mongos。注意以下参数: 132 | 133 | - `configdb`: 表示config使用的集群地址; 134 | 135 | 开始搭建: 136 | 137 | - 运行mongos进程: 138 | 139 | ```bash 140 | mongos --bind_ip 0.0.0.0 --logpath /data/mongos/mongos.log --port 27017 --configdb config/member1.example.com:27019,member3.example.com:27019,member5.example.com:27019 --fork 141 | ``` 142 | 143 | - 连接到任意一个mongos,此处我们使用`member1`: 144 | 145 | ```bash 146 | mongo --host member1.example.com:27017 147 | ``` 148 | 149 | - 将`shard1`加入到集群中: 150 | 151 | ```javascript 152 | sh.addShard("shard1/member1.example.com:27010,member3.example.com:27010,member5.example.com:27010"); 153 | ``` 154 | 155 | ### 测试分片集 156 | 157 | 上述示例中我们搭建了一个只有1个分片的分片集。在继续之前我们先来测试一下这个分片集。 158 | 159 | - 连接到分片集: 160 | 161 | ```bash 162 | mongo --host member1.example.com:27017 163 | ``` 164 | ```javascript 165 | sh.status(); 166 | ``` 167 | ```txt 168 | mongos> sh.status() 169 | --- Sharding Status --- 170 | sharding version: { 171 | "_id" : 1, 172 | "minCompatibleVersion" : 5, 173 | "currentVersion" : 6, 174 | "clusterId" : ObjectId("5e06b2509264c9792e19ea0f") 175 | } 176 | shards: 177 | { "_id" : "shard1", "host" : "shard1/member1.example.com:27010,member3.example.com:27010,member5.example.com:27010", "state" : 1 } 178 | active mongoses: 179 | "4.2.2" : 2 180 | autosplit: 181 | Currently enabled: yes 182 | balancer: 183 | Currently enabled: yes 184 | Currently running: no 185 | Failed balancer rounds in last 5 attempts: 0 186 | Migration Results for the last 24 hours: 187 | No recent migrations 188 | databases: 189 | { "_id" : "config", "primary" : "config", "partitioned" : true } 190 | ``` 191 | 192 | - 创建一个分片表: 193 | 194 | ```javascript 195 | sh.enableSharding("foo"); 196 | sh.shardCollection("foo.bar", {_id: 'hashed'}); 197 | sh.status(); 198 | ``` 199 | ```txt 200 | ... 201 | { "_id" : "foo", "primary" : "shard1", "partitioned" : true, "version" : { "uuid" : UUID("838293c5-f083-4a3b-b75e-548cfe2f6087"), "lastMod" : 1 } } 202 | foo.bar 203 | shard key: { "_id" : "hashed" } 204 | unique: false 205 | balancing: true 206 | chunks: 207 | shard1 2 208 | { "_id" : { "$minKey" : 1 } } -->> { "_id" : NumberLong(0) } on : shard1 Timestamp(1, 0) 209 | { "_id" : NumberLong(0) } -->> { "_id" : { "$maxKey" : 1 } } on : shard1 Timestamp(1, 1) 210 | ``` 211 | 212 | - 任意写入若干数据: 213 | 214 | ```javascript 215 | use foo 216 | for (var i = 0; i < 10000; i++) { 217 | db.bar.insert({i: i}); 218 | } 219 | ``` 220 | 221 | ### 向分片集加入新的分片 222 | 223 | 下面我们搭建`shard2`并将其加入分片集中,观察发生的效果。 224 | 225 | 使用类似`shard1`的方式搭建`shard2`。在`member2`/`member4`/`member6`上执行以下命令: 226 | 227 | ```bash 228 | mongod --bind_ip 0.0.0.0 --replSet shard2 --dbpath /data/shard2 --logpath /data/shard2/mongod.log --port 27011 --fork --shardsvr --wiredTigerCacheSizeGB 1 229 | ``` 230 | 231 | 用这三个实例搭建`shard2`复制集: 232 | 233 | - 任意连接到一个实例,例如我们连接到`member2.example.com`: 234 | 235 | ```bash 236 | mongo --host member2.example.com:27011 237 | ``` 238 | 239 | - 初始化`shard2`复制集。我们使用如下配置初始化复制集: 240 | 241 | ```javascript 242 | rs.initiate({ 243 | _id: "shard2", 244 | "members" : [ 245 | { 246 | "_id": 0, 247 | "host" : "member2.example.com:27011" 248 | }, 249 | { 250 | "_id": 1, 251 | "host" : "member4.example.com:27011" 252 | }, 253 | { 254 | "_id": 2, 255 | "host" : "member6.example.com:27011" 256 | } 257 | ] 258 | }); 259 | ``` 260 | 261 | - 连接到任意一个mongos。此处使用`member1`: 262 | 263 | ```bash 264 | mongo --host member1.example.com 265 | ``` 266 | 267 | - 将`shard2`加入到集群中: 268 | ```javascript 269 | sh.addShard("shard2/member2.example.com:27011,member4.example.com:27011,member6.example.com:27011"); 270 | ``` 271 | ```txt 272 | { 273 | "shardAdded" : "shard2", 274 | "ok" : 1, 275 | "operationTime" : Timestamp(1577498687, 6), 276 | "$clusterTime" : { 277 | "clusterTime" : Timestamp(1577498687, 6), 278 | "signature" : { 279 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), 280 | "keyId" : NumberLong(0) 281 | } 282 | } 283 | } 284 | ``` 285 | 286 | - 观察`sh.status()`: 287 | ```javascript 288 | sh.status(); 289 | ``` 290 | ```txt 291 | ... 292 | { "_id" : "foo", "primary" : "shard1", "partitioned" : true, "version" : { "uuid" : UUID("838293c5-f083-4a3b-b75e-548cfe2f6087"), "lastMod" : 1 } } 293 | foo.bar 294 | shard key: { "_id" : "hashed" } 295 | unique: false 296 | balancing: true 297 | chunks: 298 | shard1 1 299 | shard2 1 300 | { "_id" : { "$minKey" : 1 } } -->> { "_id" : NumberLong(0) } on : shard2 Timestamp(2, 0) 301 | { "_id" : NumberLong(0) } -->> { "_id" : { "$maxKey" : 1 } } on : shard1 Timestamp(2, 1) 302 | ``` 303 | 304 | 可以发现原本`shard1`上的两个chunk被均衡到了`shard2`上,这就是MongoDB的自动均衡机制。 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /spark-demo/SparkConnectorDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spark-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.geekbang.time 8 | SparkConnectorDemo 9 | 1.0-SNAPSHOT 10 | 11 | 12 | org.mongodb 13 | mongo-java-driver 14 | 3.12.1 15 | 16 | 17 | com.github.javafaker 18 | javafaker 19 | 1.0.1 20 | 21 | 22 | org.mongodb.spark 23 | mongo-spark-connector_2.11 24 | 2.4.1 25 | 26 | 27 | org.apache.spark 28 | spark-core_2.11 29 | 2.4.5 30 | 31 | 32 | org.apache.spark 33 | spark-sql_2.11 34 | 2.4.5 35 | 36 | 37 | 38 | 39 | 40 | maven-assembly-plugin 41 | 42 | 43 | package 44 | 45 | single 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.geekbang.time.spark.SparkDemo 53 | 54 | 55 | 56 | jar-with-dependencies 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-compiler-plugin 63 | 64 | 8 65 | 8 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /spark-demo/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/.DS_Store -------------------------------------------------------------------------------- /spark-demo/src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/main/.DS_Store -------------------------------------------------------------------------------- /spark-demo/src/main/java/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/main/java/.DS_Store -------------------------------------------------------------------------------- /spark-demo/src/main/java/org/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/main/java/org/.DS_Store -------------------------------------------------------------------------------- /spark-demo/src/main/java/org/geekbang/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/main/java/org/geekbang/.DS_Store -------------------------------------------------------------------------------- /spark-demo/src/main/java/org/geekbang/time/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/main/java/org/geekbang/time/.DS_Store -------------------------------------------------------------------------------- /spark-demo/src/main/java/org/geekbang/time/spark/Config.java: -------------------------------------------------------------------------------- 1 | package org.geekbang.time.spark; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Properties; 6 | 7 | public class Config extends Properties { 8 | public Config() { 9 | InputStream configStream = Config.class.getResourceAsStream("/config.properties"); 10 | try { 11 | this.load(configStream); 12 | } catch(IOException e) { 13 | System.out.println(e); 14 | System.exit(1); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spark-demo/src/main/java/org/geekbang/time/spark/MongoBase.java: -------------------------------------------------------------------------------- 1 | package org.geekbang.time.spark; 2 | 3 | import com.mongodb.ConnectionString; 4 | import com.mongodb.client.MongoClient; 5 | import com.mongodb.client.MongoClients; 6 | import com.mongodb.client.MongoCollection; 7 | import com.mongodb.client.MongoDatabase; 8 | 9 | public abstract class MongoBase { 10 | protected MongoClient inputClient; 11 | protected String inputDatabase; 12 | protected String inputCollection; 13 | protected MongoClient outputClient; 14 | protected String outputDatabase; 15 | protected String outputCollection; 16 | protected Config config; 17 | 18 | /** 19 | * 构造函数 20 | */ 21 | public MongoBase() { 22 | // 输入数据库相关 23 | this.config = new Config(); 24 | String input = this.config.getProperty("input"); 25 | int index = input.lastIndexOf("."); 26 | String connStr = input.substring(0, index); 27 | ConnectionString conn = new ConnectionString(connStr); 28 | this.inputClient = MongoClients.create(conn); 29 | this.inputDatabase = conn.getDatabase(); 30 | this.inputCollection = input.substring(index + 1); 31 | 32 | // 输出数据库相关 33 | String output = config.getProperty("output"); 34 | index = output.lastIndexOf("."); 35 | connStr = output.substring(0, index); 36 | conn = new ConnectionString(connStr); 37 | this.outputClient = MongoClients.create(conn); 38 | this.outputDatabase = conn.getDatabase(); 39 | this.outputCollection = output.substring(index + 1); 40 | } 41 | 42 | 43 | /** 44 | * 获取输入数据库 45 | * @return 输入数据库 46 | */ 47 | protected MongoDatabase getInputDatabase() { 48 | MongoDatabase db = this.inputClient.getDatabase(this.inputDatabase); 49 | return db; 50 | } 51 | 52 | /** 53 | * 获取输入数据表 54 | * @return 输入数据表 55 | */ 56 | protected MongoCollection getInputCollection() { 57 | MongoCollection coll = this.getInputDatabase().getCollection(this.inputCollection); 58 | return coll; 59 | } 60 | 61 | /** 62 | * 获取输出数据库 63 | * @return 输出数据库 64 | */ 65 | protected MongoDatabase getOutputDatabase() { 66 | MongoDatabase db = this.outputClient.getDatabase(this.outputDatabase); 67 | return db; 68 | } 69 | 70 | /** 71 | * 获取输出数据表 72 | * @return 输出数据表 73 | */ 74 | protected MongoCollection getOutputCollection() { 75 | MongoCollection coll = this.getOutputDatabase().getCollection(this.outputCollection); 76 | return coll; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /spark-demo/src/main/java/org/geekbang/time/spark/SparkDemo.java: -------------------------------------------------------------------------------- 1 | package org.geekbang.time.spark; 2 | 3 | import com.github.javafaker.Faker; 4 | import com.mongodb.client.FindIterable; 5 | import com.mongodb.client.MongoCollection; 6 | import com.mongodb.client.MongoCursor; 7 | import com.mongodb.client.model.Field; 8 | import com.mongodb.spark.MongoSpark; 9 | import com.mongodb.spark.config.ReadConfig; 10 | import com.mongodb.spark.config.WriteConfig; 11 | import com.mongodb.spark.rdd.api.java.JavaMongoRDD; 12 | import org.apache.spark.api.java.JavaSparkContext; 13 | import org.apache.spark.sql.Dataset; 14 | import org.apache.spark.sql.Row; 15 | import org.apache.spark.sql.SparkSession; 16 | import org.bson.Document; 17 | 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | import static com.mongodb.client.model.Aggregates.addFields; 24 | 25 | public class SparkDemo extends MongoBase { 26 | public static final int BATCH_SIZE = 100; 27 | public static final int TOTAL_COUNT = 100000; 28 | 29 | /** 30 | * 生成`TOTAL_COUNT`条模拟数据供稍后Spark计算使用 31 | */ 32 | public void mock() { 33 | MongoCollection coll = this.getInputCollection(); 34 | 35 | Faker faker = new Faker(); 36 | List data = new ArrayList(); 37 | for(int i = 0; i < TOTAL_COUNT; i++) { 38 | Document user = new Document(); 39 | user.put("name", faker.name().fullName()); 40 | user.put("address", faker.address().fullAddress()); 41 | user.put("birthday", faker.date().birthday()); 42 | user.put("favouriteColor", faker.color().name()); 43 | data.add(user); 44 | // 使用批量方式插入以提高效率 45 | if (i % BATCH_SIZE == 0) { 46 | coll.insertMany(data); 47 | data.clear(); 48 | } 49 | } 50 | if (data.size() > 0) { 51 | coll.insertMany(data); 52 | } 53 | System.out.println(String.format("%d documents generated!", TOTAL_COUNT)); 54 | } 55 | 56 | /** 57 | * 基于生成的数据在Spark中进行统计计算。 58 | */ 59 | public void spartCompute() { 60 | SparkSession spark = SparkSession.builder() 61 | .master("local") 62 | .appName("SparkDemo") 63 | .config("spark.mongodb.input.uri", this.config.getProperty("input")) 64 | .config("spark.mongodb.output.uri", this.config.getProperty("output")) 65 | .getOrCreate(); 66 | JavaSparkContext jsc = new JavaSparkContext(spark.sparkContext()); 67 | ReadConfig rc = ReadConfig.create(jsc) 68 | .withOption("readPreference.name", "secondaryPreferred") 69 | .withOption("collection", "User"); // 与input uri中一致,可省略 70 | // 加载数据时通过Aggregation获取了生日月份 71 | List pipeline = Arrays.asList(addFields(new Field("month", new Document("$month", "$birthday")))); 72 | JavaMongoRDD rdd = MongoSpark.load(jsc, rc).withPipeline(pipeline); 73 | Dataset ds = rdd.toDF(); 74 | ds.createOrReplaceTempView("User"); 75 | // 按月份计算每个月出生的人最喜欢的颜色是什么 76 | Dataset result = spark.sql("SELECT b.month AS _id, b.favouriteColor, b.qty\n" + 77 | "FROM (\n" + 78 | " SELECT a.*, \n" + 79 | " ROW_NUMBER() OVER (PARTITION BY month ORDER BY qty desc) AS seq\n" + 80 | " FROM (\n" + 81 | " SELECT month, favouriteColor, COUNT(1) AS qty\n" + 82 | " FROM User\n" + 83 | " GROUP BY month, favouriteColor\n" + 84 | " ) AS a\n" + 85 | ") AS b\n" + 86 | "WHERE b.seq = 1\n" + 87 | "ORDER BY _id ASC"); 88 | 89 | WriteConfig wc = WriteConfig.create(jsc).withOption("writeConcern.w", "majority"); 90 | MongoSpark.save(result, wc); 91 | jsc.close(); 92 | } 93 | 94 | public void display() { 95 | MongoCollection coll = this.getOutputCollection(); 96 | FindIterable result = coll.find(); 97 | for(MongoCursor iter = result.iterator(); iter.hasNext();) { 98 | Document doc = iter.next(); 99 | System.out.println(doc); 100 | } 101 | } 102 | 103 | public static void main(String[] args) throws IOException { 104 | SparkDemo demo = new SparkDemo(); 105 | demo.mock(); 106 | demo.spartCompute(); 107 | demo.display(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /spark-demo/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | input=mongodb://127.0.0.1/SparkDemo.User 2 | output=mongodb://127.0.0.1/SparkDemo.FavouriteColorStat -------------------------------------------------------------------------------- /spark-demo/src/test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/3eebedf8e12175507246b84da745f12b916bf573/spark-demo/src/test/.DS_Store -------------------------------------------------------------------------------- /transaction/read-isolation.md: -------------------------------------------------------------------------------- 1 | # 写冲突示例 2 | 3 | ## 准备文档 4 | 5 | ```javascript 6 | use test 7 | db.readIsolation.drop(); 8 | db.readIsolation.insertMany([{ 9 | x: 1 10 | }, { 11 | x: 2 12 | }]); 13 | ``` 14 | 15 | ## 读隔离 16 | 17 | 开启一个命令行窗口,在窗口中执行以下准备工作: 18 | 19 | ```javascript 20 | use test 21 | var session = db.getMongo().startSession(); 22 | session.startTransaction({readConcern: {level: "snapshot"}, writeConcern: {w: "majority"}}); 23 | var coll = session.getDatabase('test').getCollection("readIsolation"); 24 | ``` 25 | 26 | 在同一个窗口中执行: 27 | 28 | ```javascript 29 | coll.updateOne({x: 1}, {$set: {y: 1}}); 30 | coll.findOne({x: 1}); // 返回:{x: 1, y: 1} 31 | db.readIsolation.findOne({x: 1}); // 返回:{x: 1} 32 | session.abortTransaction(); 33 | ``` 34 | 35 | ## 事务的快照读(可重复读) 36 | 37 | 开启一个命令行窗口,在窗口中执行以下准备工作: 38 | 39 | ```javascript 40 | use test 41 | var session = db.getMongo().startSession(); 42 | session.startTransaction({readConcern: {level: "snapshot"}, writeConcern: {w: "majority"}}); 43 | var coll = session.getDatabase('test').getCollection("readIsolation"); 44 | ``` 45 | 46 | 在同一个窗口中执行: 47 | 48 | ```javascript 49 | coll.findOne({x: 1}); // 返回:{x: 1} 50 | db.readIsolation.updateOne({x: 1}, {$set: {y: 1}}); 51 | db.readIsolation.findOne({x: 1}); // 返回:{x: 1, y: 1} 52 | coll.findOne({x: 1}); // 返回:{x: 1} 53 | session.abortTransaction(); 54 | ``` 55 | -------------------------------------------------------------------------------- /transaction/write-conflict.md: -------------------------------------------------------------------------------- 1 | # 写冲突示例 2 | 3 | ## 准备文档 4 | 5 | ```javascript 6 | use test 7 | db.writeConflict.drop(); 8 | db.writeConflict.insertMany([{ 9 | x: 1 10 | }, { 11 | x: 2 12 | }]); 13 | ``` 14 | 15 | ## 写冲突 16 | 17 | 开启两个命令行窗口,在两个窗口中分别执行以下准备工作: 18 | 19 | ```javascript 20 | use test 21 | var session = db.getMongo().startSession(); 22 | session.startTransaction({readConcern: {level: "snapshot"}, writeConcern: {w: "majority"}}); 23 | var coll = session.getDatabase('test').getCollection("writeConflict"); 24 | ``` 25 | 26 | 在一个窗口中执行: 27 | 28 | ```javascript 29 | coll.updateOne({x: 1}, {$set: {y: 1}}); // 正常结束 30 | ``` 31 | 32 | 在另一个窗口中执行: 33 | 34 | ```javascript 35 | coll.updateOne({x: 1}, {$set: {y: 2}}); // 写冲突 36 | ``` 37 | -------------------------------------------------------------------------------- /transaction/write-wait.md: -------------------------------------------------------------------------------- 1 | # 写等待示例 2 | 3 | ## 准备文档 4 | 5 | ```javascript 6 | use test 7 | db.writeWait.drop(); 8 | db.writeWait.insertMany([{ 9 | x: 1 10 | }, { 11 | x: 2 12 | }]); 13 | ``` 14 | 15 | ## 写等待 16 | 17 | 开启一个命令行窗口,在窗口中执行以下准备工作: 18 | 19 | ```javascript 20 | use test 21 | var session = db.getMongo().startSession(); 22 | session.startTransaction({readConcern: {level: "snapshot"}, writeConcern: {w: "majority"}}); 23 | var coll = session.getDatabase('test').getCollection("writeWait"); 24 | coll.updateOne({x: 1}, {$set: {y: 1}}); // 正常结束 25 | ``` 26 | 27 | 在另个窗口中执行: 28 | 29 | ```javascript 30 | db.writeWait.updateOne({x: 1}, {$set: {y: 2}}); // 阻塞等待 31 | ``` 32 | 33 | 在原窗口中执行: 34 | 35 | ```javascript 36 | session.commitTransaction(); 37 | ``` 38 | --------------------------------------------------------------------------------