The labs on this site need JavaScript to run. The same content is written out below.
Learn › Database › Scaling and distribution
NoSQL and the document model
Split an order into three tables, or put it in one document?
Store the same data (orders and order items) in two ways. The relational way splits products, orders, and order items into three tables and joins them when reading. The document model is the way used by document databases such as MongoDB: it treats one order as one document and puts the array of items and even the product names inside it.
Partly checked against real execution · The document model was reproduced with a MySQL 8.4.11 JSON column, and 20 combinations were checked by execution. A dedicated document database was not run
Concepts in this lab
- NoSQL
- A general name for databases that do not use the relational model. They include key-value stores (Redis), document databases (MongoDB), wide-column stores (Cassandra), and graph databases. Many are designed to store data flexibly without a fixed schema and to scale out easily across several servers, but joins and complex transactions are more limited than in relational databases.
- Document model (document database)
- A way of storing data in units called documents, instead of rows and columns. A document usually has a JSON-like shape, and a value in it can hold an array or another object. So data that is read together, such as an order and its items, can be kept in one document and read in one go without a join. MongoDB is the best-known document database. If you copy the same value into several documents, you must fix every copy when the value changes, so deciding what goes inside a document and what is kept separately is the heart of the design.
- Fixed schema and flexible schema
- A schema is the structure that decides what items the data is made of and what data type each item has. A relational database decides the columns first when a table is created and accepts only rows that fit that structure, and changing the structure requires running ALTER TABLE. A document database can usually store documents even when their items differ, and this is called a flexible schema. The structure is easy to change, but a wrong name or a missing field is also stored as it is, so keeping the shape right is left to the application code or to a validation feature that you turn on separately.
- Normalization and denormalization
- Normalization is the design process of splitting tables so that the same data is not stored redundantly in several places. With redundancy, a change may be made in only one place, and the data then becomes inconsistent. Denormalization intentionally allows redundancy or merges tables for read performance. It reduces joins, but it adds the burden of updating the redundant data together.
- JOIN (INNER, OUTER)
- The operation that combines rows of two tables according to a condition. INNER JOIN includes only the rows where the condition matches on both sides. LEFT OUTER JOIN includes every row of the left table and fills in NULL when there is no match on the right. Choose between them based on whether data without a match must stay in the result.
Try these
- Default settings — In the document model, renaming a product means fixing every document that contains it.
- Renaming in the relational model — However many orders there are, only one row of the product table is changed.
- Reading one order as a document — It ends with a single document, with no join.
- Reading one order relationally — You have to join three tables to complete one order.
- Adding a new field to a document — It is stored right away without changing the structure. In return, documents end up with different shapes.
- Inserting a column that does not exist in the relational model — It is rejected with error 1054. You first have to change the table structure.
- Misspelling a field name in a document — It is stored without an error, and the quantity total becomes quietly wrong.
Running with the default settings
- The data is stored with the document model. The order_doc table has 3 documents, and one document is one order. Each document holds the person who ordered and the array of items (items) as a whole, and the product name is copied into each item.
- UPDATE order_doc SET doc = JSON_SET(…) WHERE …: It finds every document whose items array contains 'keyboard' and changes the name at that position in each document. The number of documents changed is 3. Because the product name is copied into every order document, you have to change as many documents as contain that product.
- This time all 3 were changed, so every order shows the new name. But if you stop after changing only some, the same product is left with different names in different documents. The database does not know that these values must be the same.
Result of this run
- A value copied into documents has to be found and changed in every copy each time it changes. This is the cost of making reads easy. So in the document model, you either copy only values that rarely change, or keep only the product number and look up the name separately.
- However, if you must keep a record of the product name and price at the time of the order, it is actually correct behavior that the copied value does not change later. Copying is not always bad.
- NoSQL is a general name for databases that are not relational, and the document model is one of them. Rather than one being better, you choose according to what you often read together and what changes often.
Code
-- The document model, reproduced with a MySQL JSON column. One row is one document
CREATE TABLE order_doc (id INT PRIMARY KEY, doc JSON NOT NULL);
-- Example of a document (order 1)
-- {"member": "kim",
-- "items": [{"product_id": 1, "name": "keyboard", "qty": 1},
-- {"product_id": 2, "name": "mouse", "qty": 2}]}
-- In each document, find the position of 'keyboard' in the items array and change the name at that position
UPDATE order_doc
SET doc = JSON_SET(doc, JSON_UNQUOTE(JSON_SEARCH(doc, 'one', 'keyboard', NULL, '$.items[*].name')), 'mechanical keyboard')
WHERE JSON_SEARCH(doc, 'one', 'keyboard', NULL, '$.items[*].name') IS NOT NULL;
Assumptions of this model
The document model was reproduced with a MySQL JSON column. I ran twenty combinations (two ways of storing, four things to do, and 2, 3, and 5 orders) with the same statements as on the screen on MySQL 8.4.11 on the PC used to write this lab, and checked that the following matched the screen: the error code; the number of tables and the number of result rows shown by EXPLAIN; the number of rows (documents) changed by UPDATE; the number of documents with gift; and the quantity total. I also confirmed the following facts used in the explanations by execution. Reading without a join needs three queries, one for each table. After a column is added with ALTER TABLE, the same INSERT goes in, and the existing rows also get that column. If only one document is changed, the same product is left with two different names. After a JSON_SCHEMA_VALID constraint is added, a document with a misspelled field name is rejected with error 3819. A dedicated document database (MongoDB and others) is not on this PC, so I could not run one. So what is shown here is "the shape of reads and writes when an order is stored as one document," not the exact behavior of a document database product. Query syntax, how to change a value inside an array, the schema validation feature, and support for transactions and joins across several documents differ by product and version. Which way is faster depends on the data volume, the indexes, and the product, so it is not covered, and the results on the screen are the number of places read or changed, not the speed. Other kinds of NoSQL, such as key-value stores, wide-column stores, and graph databases, are not covered.
Previous lab: Redis single-threaded execution