If you are coming from the MySQL world, DynamoDB will feel foreign at first. The mental models are different, the querying rules are different, and the things you take for granted — like filtering on any column — simply do not work the same way. This post will walk you through the key concepts so that when you sit down to build something with DynamoDB, you are not guessing.
Key-Value vs Key-Column
Let us start with the most fundamental difference. In MySQL, you store data in rows and columns. Each column is independently queryable. You can write WHERE phone = '0123456789' without thinking twice.
DynamoDB is a key-value database. Each item is stored as a single entity, identified by a unique key. Think of it like a PHP associative array:
Key: Customer1
Value:
{
"name":"Ahmad Mayahi",
"email":"ahmad@mayahi.net",
"phone":"0123456789"
}
You retrieve the entire item by its key. That is the primary access pattern.
Now, DynamoDB does let you work with individual attributes — you can read and update name or phone independently. But here is the critical difference: you cannot query on those attributes unless you explicitly set up an index for them.
If you wanted to represent this same key-value model in MySQL, it would look something like this:
CREATE TABLE customers (
`key` VARCHAR NOT NULL UNIQUE,
`value` TEXT NOT NULL,
PRIMARY KEY (`key`)
);
You would lose the ability to filter by phone or email directly. That is essentially what DynamoDB gives you by default — a fast lookup by key, and nothing else unless you ask for it.
The moment you understand that DynamoDB is designed for key-based access first and everything else second, the rest of its design decisions start making sense.
Schemaless Does Not Mean Structureless
Unlike MySQL, DynamoDB does not require you to define a schema upfront. You can add attributes to one item that do not exist on another. There is no ALTER TABLE. There are no migrations in the traditional sense.
But do not confuse schemaless with structureless. Your application still needs to know what data it expects. In a Laravel application, you would enforce structure through your models, validation rules, and data transfer objects. The schema lives in your code, not in the database.
// The database does not enforce this structure.
// Your application does.
$validated = $request->validate([
'name' => ['required', 'string'],
'email' => ['required', 'email'],
'phone' => ['required', 'string'],
]);
This is a trade-off, not a free pass. You gain flexibility. You lose the safety net of the database telling you "this column does not exist." Be deliberate about where you enforce your rules.
Tables, Items, and Attributes
DynamoDB organizes data into tables, just like MySQL. The terminology is different, but the mapping is straightforward:
- Table — same concept as MySQL.
- Item — a single record, equivalent to a row.
- Attribute — a piece of data within an item, equivalent to a column.
There is one additional concept that has no MySQL equivalent: the item collection. This is a group of items that share the same partition key. If you have an orders table partitioned by customer_id, then all orders for customer #123 form an item collection. This matters for performance and querying — you will see why in a moment.
The Primary Key
Every DynamoDB table requires a primary key. Unlike MySQL, there is no AUTO_INCREMENT. Every time you create an item, you must provide the key yourself.
The primary key can take one of two forms:
- Partition key only — a single attribute that uniquely identifies the item.
- Partition key + sort key — a composite key where the combination of both must be unique.
Here is the part that trips up MySQL developers: you can only query on the primary key. In MySQL, you can write WHERE phone = '0123456789' on any column. In DynamoDB, if phone is not part of the primary key or a secondary index, you simply cannot query on it.
// This works — querying by primary key
$result = $dynamodb->query([
'TableName' => 'customers',
'KeyConditionExpression' => 'id = :id',
'ExpressionAttributeValues' => [
':id' => ['S' => '123'],
],
]);
// Want to query by phone? You need a secondary index.
// There is no shortcut.
Partitions
When you write an item to DynamoDB, the service determines where to physically store it based on the partition key. Under the hood, DynamoDB uses a hash function to map your partition key to a specific storage partition — a slice of SSD-backed storage that is automatically replicated across multiple availability zones.
Why does this matter to you? Two reasons:
- Speed. DynamoDB can locate your data without scanning the entire table. It hashes the partition key and goes straight to the right storage node. This is why reads by primary key are so fast, regardless of table size.
- Distribution. If you choose a poor partition key — one with low cardinality, like a boolean
statusfield — all your data ends up in a few partitions, creating hot spots and throttling.
Choose a partition key with high cardinality. User IDs, order IDs, and UUIDs are good. Status fields, boolean flags, and dates are not.
The Sort Key
The sort key (also called the range key) is the second half of a composite primary key. It does exactly what the name suggests: it sorts items within a partition and enables range queries.
This is where DynamoDB starts to feel powerful. Suppose you have an orders table with customer_id as the partition key and order_date as the sort key. You can now retrieve all orders for a customer within a date range:
$result = $dynamodb->query([
'TableName' => 'orders',
'KeyConditionExpression' => 'customer_id = :customerId AND order_date BETWEEN :startDate AND :endDate',
'ExpressionAttributeValues' => [
':customerId' => ['S' => '12345'],
':startDate' => ['S' => '2023-01-01'],
':endDate' => ['S' => '2024-01-01'],
],
]);
Without a sort key, you can only fetch items by exact partition key match. With a sort key, you unlock BETWEEN, begins_with, >, <, and other range operations — but only on that one attribute.
Secondary Indexes
By now you are probably asking: what if I need to query by phone, or email, or any attribute that is not part of the primary key?
The answer is secondary indexes. A secondary index lets you define an alternative primary key for the same table, enabling queries on attributes that would otherwise be inaccessible.
If you want to find customers by phone number, you create a secondary index with phone as its partition key. DynamoDB maintains this index automatically — when you write an item to the table, the index is updated behind the scenes.
Think of it like adding a MySQL index, except in DynamoDB it is not optional. Without a secondary index, the attribute is simply not queryable. There is no table scan fallback, no slow query — it is not allowed at all.
Summary
- DynamoDB is key-value first. You access items by their primary key. Everything else requires explicit configuration.
- Schemaless does not mean structureless. Enforce your data structure in your application code, not the database.
- Items, attributes, and item collections map roughly to rows, columns, and grouped rows in MySQL — but the querying rules are fundamentally different.
- The primary key is everything. Choose it carefully. You cannot query on anything else without a secondary index.
- Partition keys determine data distribution. Pick high-cardinality keys to avoid hot spots and throttling.
- Sort keys unlock range queries within a partition. They turn DynamoDB from a simple key-value store into something much more capable.
- Secondary indexes are not optional. If you need to query on an attribute outside the primary key, you must create one. There is no workaround.