如您所知,MySQL 8.0中的重要新功能之一是文档存储。现在使用 MySQL,您可以将 JSON 文档存储在集合中,并使用 CRUD 操作对其进行管理,NoSQL现在是MySQL的一部分!不需要混合使用MongoDB和MySQL,现在您可以不使用MongoDB,而只使用MySQL整合您的应用!🙂这是NoSQL和SQL在同一数据库中的历史性会师!
要将MySQL 8.0用作文档存储,您需要安装X插件(默认情况下从8.0.11开始),此插件支持提供编程接口的 X DevAPI。使用 X 协议与 MySQL 服务器通信的客户端可以使用 X DevAPI 开发应用程序,X 协议允许在这些客户端和服务器之间建立更灵活的连接,它同时支持 SQL 和 NoSQL API,并且可以执行非阻塞异步调用。当然,MySQL提供的所有连接器以及一些非常流行的例如PHP连接器都已经与新的X协议兼容。让我们看看您应该将NoSQL与MySQL一起使用的10个主要的原因:
- MySQL关心你的数据!完全符合 ACID 标准的 NoSQL
- CRUD 操作(SQL 不再是强制性的)
- 无架构
- 文档在数据完整性方面的优势
- 允许 SQL(这对数据分析非常重要)
- 文档没有 16MB 的限制
- 最简单的查询语法
- 安全
- 简化数据库基础设施
- 您的MySQL DBA已经知道如何管理、调整和扩展MySQL
还有更多...
- 大量现成的工具
- 使 ORM 过时
- MySQL Shell
01
—
MySQL关心你的数据!完全符合 ACID 标准的 NoSQL
与传统的NoSQL数据库不同,MySQL完全符合ACID(原子性,一致性,隔离性和持久性)。大多数时候,NoSQL数据库被指责缺乏持久性,这意味着有时数据可能会在系统崩溃后丢失。事实上,也不能保证一致性。由于MySQL文档存储依赖于InnoDB存储引擎,因此文档存储受益于InnoDB的优势和健壮性。下面默认参数的设置使开箱即用的InnoDB中的数据完全持久,一旦数据被提交,它就不会丢失:
- innodb_flush_log_at_trx_commit = 1
- innodb_doublewrite = ON
- sync_binlog = 1
而且MySQL文档存储也支持原子性和隔离性,因为也支持事务!下面的例子说明了这一点:
MySQL [localhost+ ssl/docstore] JS> db.users.find()[ { "_id": "00005ad754c90000000000000001", "name": "lefred" }]1 document in set (0.0109 sec) MySQL [localhost+ ssl/docstore] JS> session.startTransaction()Query OK, 0 rows affected (0.0069 sec) MySQL [localhost+ ssl/docstore] JS> db.users.add({name: 'dim0'})Query OK, 1 item affected (0.0442 sec) MySQL [localhost+ ssl/docstore] JS> db.users.find().fields('name')[ { "name": "lefred" }, { "name": "dim0" }]2 documents in set (0.0579 sec) MySQL [localhost+ ssl/docstore] JS> session.rollback()Query OK, 0 rows affected (0.1201 sec) MySQL [localhost+ ssl/docstore] JS> db.users.find().fields('name')[ { "name": "lefred" }]1 document in set (0.0004 sec)
如您所见,MySQL使用简单的语义支持事务。
02
—
CRUD 操作(SQL 不再是强制性的)
通过新的MySQL文档存储和X协议,我们引入了管理集合和(或)关系表的新操作。这些操作称为 CRUD(创建、读取、更新、删除)操作,它们允许您在不编写任何 SQL 的情况下管理数据。我们将 CRUD 操作分为 2 组,一组对集合进行操作,另一组对表进行操作:
集合上的 CRUD 函数 | 表上的 CRUD 函数 |
add():CollectionInsertObj | insert():插入对象 |
find():CollectionFindObj | select():选择对象 |
modify():CollectionUpdateObj | update():更新对象 |
remove():CollectionDeleteObj | delete():删除对象 |
在前面的例子中,您已经看到了如何使用find()和add()。
03
—
无架构
MySQL 文档存储带您进入无架构数据的新领域。事实上,在存储文档时,您不需要事先知道所有属性,如果需要,您可以随时修改文档。由于您不需要关注表设计,因此不需要关系范式、外键、约束甚至数据类型,这使您可以进行非常快速地启动开发,由于这些文档是动态的,因此模式迁移也不再是问题,还可以忘记大量的 ALTER 语句。例如,让我们使用以下数据:
MySQL [localhost+ ssl/docstore] JS> db.users.find()[ { "_id": "00005ad754c90000000000000001", "name": "lefred" }, { "_id": "00005ad754c90000000000000003", "name": "dim0" }, { "_id": "00005ad754c90000000000000004", "name": "Dave" }, { "_id": "00005ad754c90000000000000005", "name": "Luis" }, { "_id": "00005ad754c90000000000000006", "name": "Sunny" }]5 documents in set (0.0007 sec)
现在假设我们要为所有用户添加一个新属性,您无需运行 ALTER 语句即可完成:
MySQL [localhost+ ssl/docstore] JS> db.users.modify('1').set('department', 'development')Query OK, 5 items affected (0.1910 sec)
让我们检查一下修改后的一条记录:
MySQL [localhost+ ssl/docstore] JS> db.users.find("name = 'Sunny'")[ { "_id": "00005ad754c90000000000000006", "department": "development", "name": "Sunny" }]1 document in set (0.0005 sec)
借助于这种可以灵活修改架构的特性,开发人员有了更多的自由来编写文档,因为后续他们不需要运行大量 alter语句。
04
—
文档在数据完整性方面的优势
即使无模式非常重要,有时人们也希望强制数据完整性。使用MySQL文档存储,也可以为文档创建和维护约束和外键。这里有一个例子,我们有两个集合:用户和部门,我们创建了一个 GENERATED STORED 列以用作外键:
MySQL [localhost+ ssl/docstore] SQL> alter table departments add column dept varchar(20) generated always as (doc->>"$.name") STORED ;Query OK, 2 rows affected (0.3633 sec) MySQL [localhost+ ssl/docstore] SQL> alter table users add column dept varchar(20) generated always as (doc->>"$.department") STORED ;Query OK, 5 rows affected (0.1302 sec) MySQL [localhost+ ssl/docstore] SQL> select * from users;+---------------------------------------------------------------------------------------+------------------------------+-------------+| doc | _id | dept |+---------------------------------------------------------------------------------------+------------------------------+-------------+| {"_id": "00005ad754c90000000000000001", "name": "lefred", "department": "community"} | 00005ad754c90000000000000001 | community || {"_id": "00005ad754c90000000000000003", "name": "dim0", "department": "community"} | 00005ad754c90000000000000003 | community || {"_id": "00005ad754c90000000000000004", "name": "Dave", "department": "community"} | 00005ad754c90000000000000004 | community || {"_id": "00005ad754c90000000000000005", "name": "Luis", "department": "development"} | 00005ad754c90000000000000005 | development || {"_id": "00005ad754c90000000000000006", "name": "Sunny", "department": "development"} | 00005ad754c90000000000000006 | development |+---------------------------------------------------------------------------------------+------------------------------+-------------+5 rows in set (0.0010 sec) MySQL [localhost+ ssl/docstore] SQL> select * from departments;+------------------------------------------------------------------------------------+------------------------------+-------------+| doc | _id | dept |+------------------------------------------------------------------------------------+------------------------------+-------------+| {"_id": "00005ad754c90000000000000007", "name": "development", "manager": "Tomas"} | 00005ad754c90000000000000007 | development || {"_id": "00005ad754c90000000000000008", "name": "community", "manager": "Andrew"} | 00005ad754c90000000000000008 | community |+------------------------------------------------------------------------------------+------------------------------+-------------+2 rows in set (0.0004 sec)
让我们在这些新列上添加一些索引:
MySQL [localhost+ ssl/docstore] SQL> alter table users add index dept_idx(dept);Query OK, 0 rows affected (0.0537 sec) MySQL [localhost+ ssl/docstore] SQL> alter table departments add index dept_idx(dept);Query OK, 0 rows affected (0.1278 sec)
现在,让我们创建约束,我希望如果我删除一个部门,该部门的所有用户都将被删除:
MySQL [localhost+ ssl/docstore] SQL> alter table users add foreign key (dept) references departments(dept) on delete cascade;Query OK, 5 rows affected (0.2401 sec) MySQL [localhost+ ssl/docstore] SQL> delete from departments where doc->>"$.manager" like 'Andrew';Query OK, 1 row affected (0.1301 sec) MySQL [localhost+ ssl/docstore] SQL> select * from departments;+------------------------------------------------------------------------------------+------------------------------+-------------+| doc | _id | dept |+------------------------------------------------------------------------------------+------------------------------+-------------+| {"_id": "00005ad754c90000000000000007", "name": "development", "manager": "Tomas"} | 00005ad754c90000000000000007 | development |+------------------------------------------------------------------------------------+------------------------------+-------------+1 row in set (0.0007 sec) MySQL [localhost+ ssl/docstore] SQL> select * from users;+---------------------------------------------------------------------------------------+------------------------------+-------------+| doc | _id | dept |+---------------------------------------------------------------------------------------+------------------------------+-------------+| {"_id": "00005ad754c90000000000000005", "name": "Luis", "department": "development"} | 00005ad754c90000000000000005 | development || {"_id": "00005ad754c90000000000000006", "name": "Sunny", "department": "development"} | 00005ad754c90000000000000006 | development |+---------------------------------------------------------------------------------------+------------------------------+-------------+2 rows in set (0.0006 sec)
如您所见,实现外键约束以增强数据完整性是可能的,并且非常容易。
05
—
允许 SQL(这对数据分析非常重要)
正如您在前面的例子中看到的,在MySQL Shell可以混合SQL和NoSQL,可以从一个到另一个,然后再回来。实际上,好而旧SQL仍然非常有用,我的看法是,前端的开发人员将只使用NoSQL来创建和使用被视为对象的数据,而后端的人将继续使用SQL来创建报告和分析。
为了说明MySQL操纵文档的强大功能,我将使用一个流行的餐厅集合进行演示,它来自于另一个流行的NoSQL解决方案,下面是存储在集合中的文档示例:
MySQL [localhost+ ssl/docstore] JS> db.restaurants.find().limit(1)[ { "_id": "5ad5b645f88c5bb8fe3fd337", "address": { "building": "1007", "coord": [ -73.856077, 40.848447 ], "street": "Morris Park Ave", "zipcode": "10462" }, "borough": "Bronx", "cuisine": "Bakery", "grades": [ { "date": "2014-03-03T00:00:00Z", "grade": "A", "score": 2 }, { "date": "2013-09-11T00:00:00Z", "grade": "A", "score": 6 }, { "date": "2013-01-24T00:00:00Z", "grade": "A", "score": 10 }, { "date": "2011-11-23T00:00:00Z", "grade": "A", "score": 9 }, { "date": "2011-03-10T00:00:00Z", "grade": "B", "score": 14 } ], "name": "Morris Park Bake Shop", "restaurant_id": "30075445" }]
您可以看到每家餐厅都有一组成绩(score)和一种美食风格,一个简单的查询是:每家餐厅的平均成绩是多少?(并将输出限制为 10条)
MySQL [localhost+ ssl/docstore] SQL> SELECT name, cuisine, avg(rating) FROM restaurants, -> JSON_TABLE(doc, "$" columns(name varchar(100) path "$.name", -> cuisine varchar(100) path "$.cuisine", -> nested path "$.grades[*]" -> columns (rating int path "$.score"))) -> AS jt GROUP BY name, cuisine LIMIT 10;+--------------------------------+---------------------------------+-------------+| name | cuisine | avg(rating) |+--------------------------------+---------------------------------+-------------+| Morris Park Bake Shop | Bakery | 8.2000 || Wendy'S | Hamburgers | 9.4404 || Dj Reynolds Pub And Restaurant | Irish | 9.2500 || Riviera Caterer | American | 9.0000 || Tov Kosher Kitchen | Jewish/Kosher | 17.7500 || Brunos On The Boulevard | American | 17.0000 || Kosher Island | Jewish/Kosher | 10.5000 || Wilken'S Fine Food | Delicatessen | 10.0000 || Regina Caterers | American | 9.6000 || Taste The Tropics Ice Cream | Ice Cream, Gelato, Yogurt, Ices | 8.2500 |+--------------------------------+---------------------------------+-------------+10 rows in set, 13 warnings (1.5114 sec
想看更酷的东西吗?好!给您这样一个问题:给我 10 家最好的餐厅,但它们都必须来自不同的菜系!(这意味着如果两家最好的餐厅都是两家意大利餐厅,那么我只想看到两家中最好的一家,第二家餐厅应该是不提供意大利菜的第三家)。
在MySQL 8.0文档存储中,我们可以在SQL中使用公用表表达式(CTE)来实现这一点:
MySQL [localhost+ ssl/docstore] SQL> WITH cte AS (SELECT doc->>"$.name" AS name, -> doc->>"$.cuisine" AS cuisine, -> (SELECT AVG(score) FROM JSON_TABLE(doc, "$.grades[*]" -> COLUMNS (score INT PATH "$.score")) AS r) AS avg_score -> FROM restaurants) -> SELECT *, RANK() OVER (PARTITION BY cuisine ORDER BY -> avg_score) AS `rank` -> FROM cte ORDER BY `rank`, avg_score DESC LIMIT 10;+------------------------------------------+------------------+-----------+------+| name | cuisine | avg_score | rank |+------------------------------------------+------------------+-----------+------+| Ravagh Persian Grill | Iranian | 15.6667 | 1 || Camaradas El Barrio | Polynesian | 14.6000 | 1 || Ellary'S Greens | Californian | 12.0000 | 1 || General Assembly | Hawaiian | 11.7500 | 1 || Catfish | Cajun | 9.0000 | 1 || Kopi Kopi | Indonesian | 8.6667 | 1 || Hospoda | Czech | 7.8000 | 1 || Ihop | Pancakes/Waffles | 7.2000 | 1 || New Fresco Toetillas Tommy'S Kitchen Inc | Chinese/Cuban | 7.0000 | 1 || Snowdonia Pub | English | 7.0000 | 1 |+------------------------------------------+------------------+-----------+------+10 rows in set, 13 warnings (1.4284 sec)
很酷,不是吗?所以SQL虽然不再是强制性的,但它仍然很有用!您还可以执行更多操作,例如将文档与关系表联接...等等。当然,也不要忘记我们的工程师已经实现了的JSON函数。
06
—
没有16MB的文档限制
与其他一些NoSQL解决方案不同,MySQL不会将文档的大小限制为16MB。大量用户抱怨这个限制,但现在他们有了一个新的解决方案:MySQL!MySQL 文档存储中的单个文档可以具有 1GB 的大小!它受到max_allowed_packet大小的限制。因此,如果您有非常大的文档,MySQL是您的最佳解决方案。
07
—
最简单的查询语法
MySQL Document Store的目标不是成为MongoDB的直接替代品,虽然将数据从MongoDB中迁移到MySQL非常容易。新的 CRUD API 的目标是为开发人员提供最简单的方法来编写应用程序,而无需过多地操纵数据库后端,并能够以最简单的方式查询该数据。因此,MySQL使用非常简单的语法来查询存储在MySQL 8.0中的文档。
让我们比较一下MongoDB和MySQL中的相同查询,先看看MongoDB中的例子:
> db.restaurants.find({"cuisine": "French", "borough": { $not: /^Manhattan/} }, {"_id":0, "name": 1,"cuisine": 1, "borough": 1}).limit(2){ "borough" : "Queens", "cuisine" : "French", "name" : "La Baraka Restaurant" }{ "borough" : "Queens", "cuisine" : "French", "name" : "Air France Lounge" }
再在 MySQL 中执行同样的操作:
MySQL [localhost+ ssl/docstore] JS> restaurants.find("cuisine='French' AND borough!='Manhattan'").fields(["name","cuisine","borough"]).limit(2)[ { "borough": "Queens", "cuisine": "French", "name": "La Baraka Restaurant" }, { "borough": "Queens", "cuisine": "French", "name": "Air France Lounge" }]2 documents in set (0.0853 sec)
在MySQL中的语法更易懂,不是吗?这就是我们使用该语法的原因!
08
—
安全
默认情况下,MySQL文档存储已经非常安全:强root密码、密码策略、角色、SSL,还有多种备份解决方案。
当您安装和启动MySQL实例时,将创建一个SSL证书,以通过安全链接与客户端进行通信。此外,超级特权用户(root)将自动拥有一个强密码,该密码将存储在错误日志中,并且必须在首次登录时进行更改,新密码默认必须遵循一些安全规则。
2018-06-14T12:47:55.120634Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: (hdirvypB1iw
现在我们可以使用该密码进行连接并设置它执行我们想要的操作:
[root@mysql3 mysql]# mysqlsh root@127.0.0.1Creating a session to 'root@127.0.0.1'Enter password: ************Fetching schema names for autocompletion... Press ^C to stop.Error during auto-completion cache update: You must reset your password using ALTER USER statement before executing this statement.Your MySQL connection id is 0 (X protocol)No default schema selected; type \use to set one.MySQL Shell 8.0.11 Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners. Type '\help' or '\?' for help; '\quit' to exit. MySQL [127.0.0.1+] JS> session.sql('set password="MyV3ryStr0gP4ssw0rd%"')Query OK, 0 rows affected (0.3567 sec)
现在我们可以退出并再次登录,这次您可以看到提示正在使用 SSL,我们可以很容易地验证这一点:
[root@mysql3 mysql]# mysqlsh root@127.0.0.1Creating a session to 'root@127.0.0.1'Enter password: ****Fetching schema names for autocompletion... Press ^C to stop.Your MySQL connection id is 9 (X protocol)Server version: 8.0.11 MySQL Community Server - GPLNo default schema selected; type \use to set one.MySQL Shell 8.0.11 Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners. Type '\help' or '\?' for help; '\quit' to exit. MySQL [127.0.0.1+ ssl] JS> \sMySQL Shell version 8.0.11 Session type: XConnection Id: 9Default schema: Current schema: Current user: root@localhostSSL: Cipher in use: DHE-RSA-AES128-GCM-SHA256 TLSv1.2Using delimiter: ;Server version: 8.0.11 MySQL Community Server - GPLProtocol version: X protocolClient library: 8.0.11Connection: 127.0.0.1 via TCP/IPTCP port: 33060Server characterset: utf8mb4Schema characterset: utf8mb4Client characterset: utf8mb4Conn. characterset: utf8mb4Uptime: 7 min 9.0000 sec
如果您还需要其他安全功能,请查看MySQL企业版。
09
—
简化数据库基础架构
采用MySQL,您不必同时维护多个系统,避免将重复的数据从一个系统传输到另一个系统,因为MySQL文档存储既可以执行SQL,又可以执行NoSQL,它可以整合RDBMS和NoSQL数据库。
此外,从前面的例子中,您可以看到MySQL可以混合存储表和集合,也可以对文档运行复杂的SQL。而且如果您真地想把相同的数据拆分成不同的系统,可以使用非常流行且功能强大的MySQL复制。另外,使用MySQL InnoDB Cluster也可以实现简单的HA架构。
10
—
您的 MySQL DBA 已经知道如何管理、调整和扩展 MySQL
最后,如果您的团队已经正在运维MySQL 实例,并且您希望(或需要)使用 NoSQL,那么还有什么是比仅使用 MySQL 8.0 的 CRUD 操作,并继续使用所有已经掌握的知识来维护您的基础设施更简单的事情呢?相同的备份,相同的监控,相同的方法来解决最终问题。
无需投资新的团队或知识来维护另一个系统。MySQL 8.0 文档存储为您提供两全其美的功能,让开发人员和 DBA/OPS团队都感到高兴。
我鼓励您尽快测试它,不要犹豫,向我们发送您的反馈!
看完了这篇文章后,您对MySQL 8.0和NoSQL的观点有变化了吗?译者邀请您进行一个投票: