01
چرا پارتیشنبندی — و کِی نباید عجله کردWhy partition — and when not
to rush
نکتهای که شاید باورتان نشود: در SQL Server، پارتیشنبندی در درجهی اول یک ابزار
مدیریت و نگهداری است، نه ابزاری برای تندتر کردنِ کوئریها. این جملهی کلیدیِ فصل ۱۶ کتابِ Pro SQL
Server Internals است — و اگر آن را جدی نگیرید، تمام تصمیمگیریهایتان دربارهی پارتیشنبندی، از همان
ابتدا اشتباه خواهد بود.
Here's something you might not believe: in SQL Server, partitioning is primarily a
management and maintenance tool — not a query-speed hack. This is the key takeaway from Chapter 16 of
Pro SQL Server Internals — and if you ignore it, every partitioning decision you make will be wrong
from the start.
دلایلِ واقعی برای پارتیشنبندیThe real reasons to
partition
۱. نگهداریِ تکهتکه: بکاپ، بازسازی ایندکس، بهروزرسانی آمار — همه روی یک پارتیشن، نه کلِ جدولِ
غولپیکر.
۲. حذف یا آرشیوِ سریع: بهجای DELETE کردنِ میلیونها سطر، کلِ یک پارتیشن را در چند
میلیثانیه SWITCH میکنید.
۳. ذخیرهسازیِ طبقهای: دادهٔ داغ روی SSD، دادهٔ کهنه روی HDD ارزان.
۴. رد کردنِ پارتیشنهای نامربوط: کوئریهای بازهای میتوانند پارتیشنهایی را که به کارشان نمیآیند،
از پلن اجرا کنار بگذارند — اما این معمولاً یک سودِ جانبی است، نه هدفِ اصلی.
1. Chunk-level maintenance: backups, index rebuilds, stats updates — all on one partition, not the
giant whole table.
2. Fast purge or archive: instead of DELETEing millions of rows, you
SWITCH a whole partition away in milliseconds.
3. Tiered storage: hot data on SSD, cold data on cheap HDD.
4. Pruning irrelevant partitions: range queries can exclude partitions that aren't needed from the
execution plan — but this is usually a side benefit, not the main goal.
کِی پارتیشنبندی نکنیمWhen not to partition
اگر جدول شما فقط «چند میلیون سطر» دارد، پارتیشنبندی سربار اضافه میکند بدون اینکه سودی
داشته باشد. پارتیشنبندی برای جدولهای واقعاً بزرگ است — دهها میلیون تا میلیاردها سطر، جایی که
نگهداریِ کلِ جدول به یک دردسر تبدیل شده. قبل از پارتیشنبندی، اول ایندکسها را بررسی کنید (فصل قبلِ همین
مسیر).
If your table only has "a few million rows", partitioning adds overhead with no real
benefit.
Partitioning is for truly large tables — tens of millions to billions of rows, where maintaining
the
whole table has become a pain. Before partitioning, check your indexes first (the previous chapter of this
path).
قاعدهٔ سرانگشتی: قبل از پارتیشنبندی یک سوال بپرسید: «مشکل من نگهداریِ جدول است یا
کندیِ یک کوئری؟» اگر جواب «نگهداری» است، پارتیشنبندی احتمالاً راهگشاست. اگر «کوئری» است، اول ایندکس، بعد
پلن اجرا، و در آخر پارتیشنبندی.
Rule of thumb: before partitioning, ask one question: "is my problem table maintenance
or
a slow query?" If it's maintenance, partitioning probably helps. If it's the query, check indexes first,
then
the execution plan, and partitioning last.
.NET
وقتی یک کوئری EF Core روی جدولی با دهها میلیون سطر کند میشود، ترتیب کار این است:
اول
ایندکسها را بررسی کنید، بعد پلن اجرا را ببینید (با SET STATISTICS IO ON تعداد
logical reads را چک کنید)، و فقط اگر جدول واقعاً بزرگ است و مشکل نگهداری دارید،
سراغ پارتیشنبندی بروید. کد LINQ شما دستنخورده میماند — پارتیشنبندی برای آن شفاف است.
When an EF Core query gets slow on a table with tens of millions of rows, the order is:
first check your indexes, then review the execution plan (use
SET STATISTICS IO ON to
check logical reads), and only if the table is truly large and you have a maintenance
problem, consider partitioning. Your LINQ code stays untouched — partitioning is transparent to
it.
03
راهبردهای پارتیشنبندی — RANGE، LIST، HASH و KEYPartitioning
strategies — RANGE, LIST, HASH, and KEY
چهار روش پارتیشنبندی در دنیای دیتابیسها وجود دارد: RANGE، LIST، HASH و KEY. اما هر موتوری همهٔ آنها را پشتیبانی نمیکند. در SQL Server فقط RANGE در دسترس است؛ MySQL هر چهار روش را دارد که KEY مختص خودش است.
There are four partitioning methods in the database world: RANGE, LIST, HASH, and KEY. But not every engine supports all of them. In SQL Server, only RANGE is available; MySQL has all four, with KEY being its own.
RANGE
هر پارتیشن، یک بازهٔ پیوسته را پوشش میدهد. برای دادههای سریزمانی (لاگ، سفارش،
سنسور) انتخابِ طبیعی است. کوئریهای بازهای (BETWEEN, >=) مستقیم به یک یا چند
پارتیشن میرسند و بقیه حذف میشوند.
Each partition covers a continuous range. The natural choice for time-series data
(logs, orders, sensors). Range queries (BETWEEN, >=) map directly onto one or
a few partitions, and the rest are pruned.
LIST
مقدار ستون پارتیشن با لیست مقادیرِ تعیینشده برای هر پارتیشن مقایسه میشود و در صورت
تطابق، سطر به همان پارتیشن میرود — مثلاً پارتیشن اروپا فقط سطرهایی را میگیرد که ستونِ کشورشان
DE, FR, IT باشد. این روش وقتی دستههای طبیعی و کمتعداد دارید (منطقه، وضعیت، نوع اشتراک) عالی
است.
The partition column value is compared against each partition's defined value list and if
it matches, the row goes to that partition — for example, the Europe partition only gets rows where the
country column is DE, FR, IT. This method is great when you have natural, low‑cardinality
groups (region, status, subscription tier).
HASH
شما یک عبارتِ عددی روی ستون تعریف میکنید (مثلاً YEAR(OrderDate)) و
باقیماندهٔ تقسیم آن بر تعداد پارتیشنها، شمارهٔ پارتیشن را مشخص میکند. توزیع سطرها بین پارتیشنها
یکنواخت است، اما ترتیب دادهها از بین میرود و کوئریهای بازهای نمیتوانند پارتیشنهای نامربوط را از پلن
اجرا کنار بگذارند.
You define an integer expression on the column (e.g. YEAR(OrderDate)) and the
remainder of its division by the partition count determines the partition number. Rows are evenly
distributed across partitions, but data order is lost and range queries cannot exclude irrelevant
partitions from the execution plan.
KEY (MySQL only)
شبیه HASH، اما MySQL تابعِ هشِ داخلی خودش را استفاده میکند و شما نیازی به نوشتن
عبارت ندارید. ستونهای غیرعددی (رشته، تاریخ) را هم میپذیرد.
Like HASH, but MySQL uses its own internal hashing function and you don't write an
expression. It accepts non-integer columns (strings, dates).
امتحان کنید — یک سطر کجا مینشیند؟Try it — where does a row
land?
بین چهار راهبرد جابهجا شوید و
ببینید هر سطر نمونه در کدام پارتیشن فرود میآید.Switch between the four strategies
and watch each sample row land in its partition.
Q1
Jan – Mar
2025-02-10
Q2
Apr – Jun
2025-05-03
Q3
Jul – Sep
2025-08-21
Q4
Oct – Dec
2025-11-30
مقدار ستون پارتیشن با مرزهای تعیینشده مقایسه میشود و بر اساس قرار گرفتن در یکی از
بازهها، به همان پارتیشن میرود، این روش برای کوئریهای بازهای بسیار کارآمد است
The partition column value is compared against the defined boundaries and based on which
range it falls into, it goes to that partition, this method is very efficient for range queries
AMER
USBR
EMEA
DEFR
APAC
JPIN
مقدار ستون پارتیشن با لیست مقادیر هر پارتیشن مقایسه میشود و در صورت تطابق، به آن پارتیشن
میرود
The partition column value is compared against each partition's value list and if it
matches, it goes to that partition
bucket 0
year(col) % 4 = 0
bucket 1
year(col) % 4 = 1
bucket 2
year(col) % 4 = 2
bucket 3
year(col) % 4 = 3
عبارت عددیِ دلخواه شما روی ستون اعمال میشود و باقیماندهٔ تقسیم آن بر تعداد پارتیشنها،
شمارهٔ سطل را مشخص میکند، توزیع یکنواخت است اما حذف بازهای ممکن نیست
Your custom integer expression is applied to the column and the remainder of its division
by the partition count determines the bucket number, distribution is even but range pruning is not
possible
bucket 0
sku:B-205
bucket 1
order:8841
bucket 2
user:42
bucket 3
user:113
MySQL از تابع هش داخلی خود روی ستون استفاده میکند و شمارهٔ سطل را مشخص میکند، نیازی به
نوشتن عبارت نیست و روی هر نوع ستونی کار میکند
MySQL applies its own internal hash function to the column and determines the bucket
number, no expression needed and it works on any column type
علاوه بر چهار روش اصلی، MySQL دو قابلیتِ تکمیلی نیز دارد که در ادامه توضیح داده
شدهاند
In addition to the four main methods, MySQL also has two complementary features explained
below
RANGE COLUMNS / LIST COLUMNSRANGE COLUMNS / LIST
COLUMNS
اینها در واقع نسخههای توسعهیافتهی همان RANGE و LIST هستند، نه روشهای جداگانه.
تفاوتشان این است که بهجای یک عبارتِ عددی، خودِ ستون را میپذیرند — از جمله ستونهای رشتهای و
تاریخی. برای پارتیشنبندی مستقیم بر اساس ستونِ تاریخ، نیازی به TO_DAYS ندارید و سادهتر
است.
These are actually extended versions of the same RANGE and LIST, not separate methods.
The difference is that they accept the column itself instead of an integer expression — including
string and date columns. For partitioning directly on a date column, you don't need TO_DAYS
and it's simpler.
Subpartitioning (پارتیشنِ تو در تو)Subpartitioning
این هم یک روشِ جداگانه نیست؛ بلکه یک لایهی اضافی روی پارتیشنبندیِ موجود است. در MySQL
میتوانید هر پارتیشنِ RANGE یا LIST را یک بار دیگر با HASH یا KEY زیرپارتیشن کنید. مثلاً
پارتیشنبندیِ اصلی بر اساس ماه، و زیرپارتیشن بر اساس CustomerID. کاربردی است، اما پیچیدگیِ
مدیریت را چند برابر میکند.
This is not a separate method either; it's an additional layer on top of existing
partitioning. In MySQL, you can subpartition each RANGE or LIST partition once more with HASH or
KEY. For example, main partitioning by month, and subpartitioning by CustomerID. Useful,
but it multiplies management complexity.
یک نکتهٔ مهم: در MySQL چیزی به نام «پارتیشنبندی تاریخ» به عنوان یک روشِ مستقل وجود ندارد. هر وقت در
مستندات یا مقالات میبینید که از «پارتیشنبندی بر اساس تاریخ» صحبت میشود، منظور همان روش RANGE است
که روی یک ستونِ تاریخ یا یک عبارت مثل TO_DAYS(date_col) اعمال شده. در SQL Server نیز دقیقاً به
همین شکل کار میکند — تنها راهِ پارتیشنبندیِ زمانی، استفاده از RANGE است.
Important note: MySQL does not have a separate method called "date partitioning". Whenever you see
"date-based partitioning" mentioned in documentation or articles, it means the RANGE method applied
to a date column or an expression like TO_DAYS(date_col). SQL Server works exactly the same way
— the only way to partition by time is using RANGE.
04
پارتیشنبندی در SQL Server — جدولها، ویوها و فراترPartitioning in
SQL Server — tables, views, and beyond
SQL Server دو مکانیزم مجزا برای پارتیشنبندی دارد و درک تفاوتشان برای هر توسعهدهندهای
ضروری است: Partitioned Tables (روش جدیدتر و قدرتمندتر) و Partitioned Views (روش قدیمیتر، اما
هنوز هم کاربردی). بیایید اول این دو را کنار هم ببینیم، بعد سراغ جزئیات هرکدام برویم.
SQL Server has two separate mechanisms for partitioning, and understanding the
difference is essential for every developer: Partitioned Tables (the newer, more powerful approach) and
Partitioned Views (the older method, but still very much alive). Let's first look at them side by side,
then dive into the details of each.
Partitioned TablesPartitioned Tables
یک جدول منطقی، چند تکهٔ فیزیکی. از دید کاربر، یک جدول واحد میبینید. اما در
پشتصحنه، داده بین چند پارتیشن توزیع شده که هرکدام روی یک Filegroup جداگانه قرار دارند. موتور با
کمک Partition Function و Partition Scheme، مدیریت کامل این ساختار را بهعهده دارد.
One logical table, several physical pieces. From the user's perspective, it's a
single table. Behind the scenes, data is distributed across multiple partitions, each sitting on its own
filegroup. The engine handles the entire management using a partition function and a partition
scheme.
Partitioned ViewsPartitioned Views
چند جدول جداگانه، یک ویو. شما چند جدول مستقل میسازید (مثلاً
Orders_2023، Orders_2024، Orders_2025)، روی هرکدام یک
CHECK CONSTRAINT برای ستون پارتیشن تعریف میکنید، و در نهایت یک ویو با UNION ALL
روی همهٔ آنها میسازید. وقتی کوئری با WHERE اجرا میشود، موتور جدولهای نامربوط را از پلن
اجرا کنار میگذارد.
Several separate tables, one view. You create independent tables (e.g.
Orders_2023, Orders_2024, Orders_2025), define a
CHECK CONSTRAINT on the partition column for each, and finally build a view that
UNION ALLs them. When a query with a WHERE clause runs, the engine excludes
irrelevant tables from the execution plan.
۱. Partitioned Tables — آجر به آجر
1. Partitioned Tables — brick by brick
برای ساختن یک جدول پارتیشنشده در SQL Server، سه مؤلفهٔ کلیدی داریم:
Building a partitioned table in SQL Server requires three key components:
تابع پارتیشن
Partition Function
Partition Function نوع ستون و مرزهای بین پارتیشنها را مشخص میکند. در این مرحله، تنها یک قانون ریاضی تعریف شدهاست — هنوز به هیچ جدولی متصل نشده.
The partition function defines the column type and the boundaries between partitions. At this stage, it's just a mathematical rule — not yet attached to any table.
طرح پارتیشن
Partition Scheme
Partition Scheme تابع را به Filegroup ها نگاشت میکند — یعنی مشخص میکند هر پارتیشن روی کدام فایل دیسک قرار بگیرد. اینجاست که Tiered Storage ممکن میشود.
The partition scheme maps the function onto filegroups — determining which disk file each partition lives on. This is where Tiered Storage becomes possible.
ستون پارتیشن
Partitioning Column
ستونی که جدول بر اساس آن پارتیشنبندی میشود — مثلاً OrderDate. با ON scheme(column) جدول را روی طرح میسازید. این ستون قلب ماجراست و باید در هر کلید یکتا (از جمله Primary Key) حضور داشته باشد.
The column by which the table is partitioned — for example, OrderDate. You create the table using ON scheme(column). This column is the heart of the matter and must be part of every unique key (including the Primary Key).
sql — partitioned table, three steps
-- 1) Partition function: the rule + boundaries
CREATE PARTITION FUNCTION pf_OrdersByYear (date)
AS RANGE RIGHT FOR VALUES
('2023-01-01', '2024-01-01', '2025-01-01');
-- → 4 partitions: <2023, 2023–2024, 2024–2025, 2025+
-- 2) Partition scheme: map partitions to filegroups
CREATE PARTITION SCHEME ps_Orders
AS PARTITION pf_OrdersByYear TO
([FG_Archive], [FG_2023], [FG_2024], [FG_Current]);
-- 3) Create the table ON the scheme — OrderDate is the key
CREATE TABLE dbo.Orders (
Id bigint IDENTITY NOT NULL,
OrderDate date NOT NULL,
CustomerId int NOT NULL,
Total decimal(12,2) NOT NULL,
CONSTRAINT PK_Orders PRIMARY KEY (Id, OrderDate) -- key must include OrderDate!
) ON ps_Orders(OrderDate);
۲. Partitioned Views — روشِ قدیمیتر
2. Partitioned Views — the older technique
پیش از ورود Partitioned Tables به SQL Server، این روش تنها راهِ ممکن بود. اما هنوز هم کاربرد خودش را دارد؛ بهویژه وقتی که نیاز دارید جدولهای عضو، ساختارِ کمی متفاوت داشته باشند یا روی سرورهای جداگانه (از طریق Linked Servers) قرار گیرند.
Before Partitioned Tables arrived in SQL Server, this was the only option. It's still useful, especially when member tables need slightly different structures or need to reside on different servers (via Linked Servers).
sql — partitioned view with CHECK constraints
-- each member table has its own CHECK constraint to enforce the partition range
CREATE TABLE dbo.Orders_2023 (
Id int NOT NULL PRIMARY KEY,
OrderDate date NOT NULL,
CONSTRAINT CK_Orders_2023 CHECK
(OrderDate >= '2023-01-01' AND OrderDate < '2024-01-01')
);
CREATE TABLE dbo.Orders_2024 (
Id int NOT NULL PRIMARY KEY,
OrderDate date NOT NULL,
CONSTRAINT CK_Orders_2024 CHECK
(OrderDate >= '2024-01-01' AND OrderDate < '2025-01-01')
);
CREATE TABLE dbo.Orders_2025 (
Id int NOT NULL PRIMARY KEY,
OrderDate date NOT NULL,
CONSTRAINT CK_Orders_2025 CHECK
(OrderDate >= '2025-01-01' AND OrderDate < '2026-01-01')
);
GO
-- the view unions all member tables
CREATE VIEW dbo.AllOrders AS
SELECT * FROM dbo.Orders_2023
UNION ALL
SELECT * FROM dbo.Orders_2024
UNION ALL
SELECT * FROM dbo.Orders_2025;
-- query through the view: optimizer uses CHECK constraints to eliminate irrelevant tables
SELECT * FROM dbo.AllOrders
WHERE OrderDate >= '2024-06-01' AND OrderDate < '2024-07-01';
-- execution plan: only Orders_2024 is touched — the other tables are pruned ✓
۳. مقایسه و استفادهٔ ترکیبی
3. Comparison and using them together
حالا که با هر دو مکانیزم آشنا شدید، این جدول کمک میکند در یک نگاه تفاوتهای کلیدی را ببینید و تصمیم بگیرید کدام یک برای سناریوی شما مناسبتر است.
Now that you're familiar with both mechanisms, this table helps you see the key differences at a glance and decide which one fits your scenario better.
|
Partitioned Tables |
Partitioned Views |
| ساختارStructure |
یک جدول منطقی، چند تکهٔ فیزیکیOne logical table, multiple physical pieces |
چند جدول جداگانه، یک ویوSeveral separate tables, one view |
| انعطافِ ساختاریSchema flexibility |
همهٔ پارتیشنها ساختار یکسانی دارندAll partitions share the same schema |
هر جدول عضو میتواند ساختارِ کمی متفاوت داشته باشدEach member table can have a slightly different schema |
| مدیریتManagement |
موتور همهچیز را خودکار مدیریت میکندThe engine handles everything automatically |
شما باید جدولهای عضو و ویو را دستی مدیریت کنیدYou must manage member tables and the view manually |
|
عملیاتِ SWITCH
SWITCH operation
[1]
|
پشتیبانی میشود — انتقالِ آنیِ یک پارتیشن به جدول دیگرSupported — instant transfer of a partition to another table |
پشتیبانی نمیشود — باید جدول را حذف و ویو را تغییر دهیدNot supported — you must drop the table and alter the view |
| سرورهای مختلفDifferent servers |
فقط در یک نمونهٔ SQL ServerOnly within one SQL Server instance |
با Linked Servers، جدولهای عضو میتوانند روی سرورهای جدا باشندWith Linked Servers, member tables can reside on separate servers |
| کِی استفاده کنیمWhen to use |
گزینهٔ پیشفرض برای اکثر سناریوهاThe default choice for most scenarios |
وقتی به ساختارهای متفاوت یا سرورهای جداگانه نیاز داریدWhen you need different schemas or separate servers |
[1]
SWITCH عملیاتی است که یک پارتیشن کامل را در حد میلیثانیه از یک جدول به جدول دیگر منتقل میکند — بدون اینکه حتی یک سطر جابهجا شود. فقط متادیتا (اشارهگرهای کاتالوگ) تغییر میکند. این ویژگی برای آرشیو کردنِ سریعِ دادههای قدیمی (الگوی Sliding Window) بسیار ارزشمند است.
SWITCH is an operation that transfers an entire partition from one table to another in milliseconds — without moving a single row. Only metadata (catalog pointers) changes. This feature is extremely valuable for quickly archiving old data (the Sliding Window pattern).
استفادهٔ ترکیبی: در عمل، میتوانید از هر دو روش با هم بهره ببرید. مثلاً دادههای «داغ» را در یک جدول پارتیشنشده نگه دارید و یک ویوی پارتیشنشده بسازید که این جدول را با یک جدول قدیمیِ جداگانه (روی یک Filegroup آرشیو) ترکیب کند. این رویکرد، انعطافپذیری هر دو روش را در اختیار شما قرار میدهد.
Using them together: in practice, you can combine both approaches. For example, keep "hot" data in a partitioned table and create a partitioned view that combines it with a separate older table (on an archive filegroup). This approach gives you the flexibility of both methods.
۴. Tiered Storage — داغ روی SSD، کهنه روی HDD
4. Tiered Storage — hot on SSD, cold on HDD
یادتان هست که Partition Scheme هر پارتیشن را به یک Filegroup متصل میکند؟ این یعنی میتوانید Filegroupهای مختلف را روی دیسکهای متفاوت قرار دهید — یکی روی SSDِ گرانقیمت و سریع، دیگری روی HDDِ ارزانتر و کندتر. دادهی امسال روی SSD مینشیند و کوئریهای داغ را سریع پاسخ میدهد؛ دادهی ۵ سال پیش روی HDD میرود و هزینهی ذخیرهسازی را پایین میآورد. همین.
Remember that the partition scheme maps each partition to a filegroup? This means you can place different filegroups on different disks — one on expensive, fast SSD, another on cheaper, slower HDD. This year's data lives on SSD and serves hot queries quickly; 5-year-old data goes to HDD and keeps storage costs down. That's it.
🔥 SSD · HOT · fast
پارتیشنهای ۲۰۲۵ و ۲۰۲۶: دادهای که همین الان کوئری میشود. سریع، گرانتر، اما حجمش کم است.
2025 and 2026 partitions: data being queried right now. Fast, more expensive, but smaller in size.
❄ HDD · COLD · cheap
پارتیشنهای قدیمیتر: دادهای که بهندرت لمس میشود. کندتر، ارزانتر، اما حجمش زیاد است.
Older partitions: data that's rarely touched. Slower, cheaper, but larger in size.
۵. Sliding Window — آرشیوِ آنی، بدون DELETE
5. Sliding Window — instant archive, no DELETE
این یکی از کاربردیترین الگوهای پارتیشنبندی است — و همان دلیلی که بسیاری از تیمها اصلاً سراغ پارتیشنبندی میروند. فرض کنید تصمیم دارید همیشه ۱۲ ماهِ اخیر را در جدول اصلی نگه دارید. هر ماه، یک پارتیشنِ جدید برای دادههای تازه اضافه میکنید و قدیمیترین پارتیشن را با ALTER TABLE ... SWITCH آنی به یک جدول آرشیو منتقل میکنید. این عملیات فقط متادیتا را تغییر میدهد و حتی اگر پارتیشن صدها میلیون سطر داشته باشد، فقط چند میلیثانیه طول میکشد. خبری از DELETE های سنگین نیست، لاگ تراکنش باد نمیکند، و قفلِ طولانیمدتی روی جدول نمیآید.
This is one of the most practical partitioning patterns — and the very reason many teams adopt partitioning in the first place. Suppose you decide to keep only the last 12 months in the main table. Each month, you add a fresh partition for new data and ALTER TABLE ... SWITCH the oldest partition instantly to an archive table. This operation changes only metadata and takes milliseconds, even if the partition holds hundreds of millions of rows. No heavy DELETEs, no transaction log bloat, and no long‑lasting table locks.
جدول اصلی — OrdersMain table — Orders
P1 (Jan)
P2 (Feb) ← moved out
P3 (Mar)
P4 (Apr)
جدول آرشیو — OrdersArchiveArchive table — OrdersArchive
P2 (Feb) — ۸ میلیون سطر، در ۲ میلیثانیه!P2 (Feb) — 8 million rows, in 2ms!
sql — the sliding window in action
-- archive table must have EXACTLY the same structure as the main table
CREATE TABLE dbo.OrdersArchive (/* identical schema */);
-- instant move: metadata-only, milliseconds even for huge partitions
ALTER TABLE dbo.Orders
SWITCH PARTITION 1 TO dbo.OrdersArchive;
-- remove the now‑empty partition boundary (merge it with the previous one)
ALTER PARTITION FUNCTION pf_OrdersByYear()
MERGE RANGE ('2023-01-01');
-- add a fresh partition for the upcoming month/year
ALTER PARTITION FUNCTION pf_OrdersByYear()
SPLIT RANGE ('2026-01-01');
۶. Potential Issues — تلههایی که باید بدانید
6. Potential Issues — traps to know about
کلید یکتا باید شامل ستون پارتیشن باشد
Unique keys must include the partition column
این مهمترین محدودیت است: هر PRIMARY KEY یا UNIQUE روی جدول پارتیشنشده باید ستون پارتیشن را هم داشته باشد. پس نمیتوانید PRIMARY KEY (Id) داشته باشید — باید PRIMARY KEY (Id, OrderDate) باشد. این را از روز اول در مدل EF Core لحاظ کنید.
This is the most important constraint: every PRIMARY KEY or UNIQUE constraint on a partitioned table must include the partition column. So you can't have PRIMARY KEY (Id) — it must be PRIMARY KEY (Id, OrderDate). Bake this into your EF Core model from day one.
اگر ستون پارتیشن در WHERE نباشد، رد شدن اتفاق نمیافتد
If the partition column is missing from WHERE, pruning does not occur
رایجترین اشتباه این است که کوئری درست مینویسید و جواب هم درست میگیرید، اما متوجه نمیشوید موتور همهٔ پارتیشنها را خوانده است. چون ستون پارتیشن در فیلتر نیست، موتور راهی برای رد کردنِ پارتیشنهای نامربوط ندارد. WHERE YEAR(OrderDate) = 2025 هم دقیقاً همین مشکل را دارد — تابع، مقایسه با مرزهای پارتیشن را غیرممکن میکند. راهحل: همیشه مستقیماً روی خودِ ستون مقایسه کنید، مثلاً WHERE OrderDate >= '2025-01-01' AND OrderDate < '2026-01-01'.
The most common mistake is writing a valid query that returns correct results, without realizing the engine has read every partition. Because the partition column isn't in the filter, the engine has no way to prune irrelevant partitions. WHERE YEAR(OrderDate) = 2025 has the exact same issue — the function makes boundary comparison impossible. The fix: always compare the column directly, e.g. WHERE OrderDate >= '2025-01-01' AND OrderDate < '2026-01-01'.
ایندکسهای aligned و non‑aligned
Aligned vs non‑aligned indexes
ایندکسی که از همان Partition Scheme استفاده میکند، aligned نامیده میشود. SWITCH فقط با ایندکسهای aligned کار میکند. اگر ایندکسی روی Scheme دیگری بسازید (non‑aligned)، نمیتوانید پارتیشن را سوییچ کنید مگر اینکه اول آن ایندکس را حذف کنید.
An index using the same partition scheme is called aligned. SWITCH only works with aligned indexes. If you build an index on a different scheme (non‑aligned), you cannot switch the partition unless you drop that index first.
سوییچ، ساختارِ دقیقاً یکسان میخواهد
SWITCH needs exactly matching structure
جدول مقصدِ SWITCH باید دقیقاً همان ساختار، همان ایندکسهای aligned، همان CHECK CONSTRAINTها و همان Filegroup را داشته باشد. کوچکترین تفاوتی، عملیات را خراب میکند.
The SWITCH target table must have exactly the same structure, the same aligned indexes, the same CHECK CONSTRAINTs, and the same filegroup. The slightest difference breaks the operation.
.NET
EF Core بهطور خودکار تابع و طرح پارتیشن نمیسازد — این کارِ Migration دستی یا DBA است. اما EF Core از PRIMARY KEYهای ترکیبی پشتیبانی میکند: builder.HasKey(o => new { o.Id, o.OrderDate }); و این دقیقاً همان چیزی است که پارتیشنبندی SQL Server نیاز دارد. کد LINQ شما دستنخورده میماند — همهچیز شفاف است.
EF Core won't auto‑create partition functions or schemes — that's manual migration or DBA work. But EF Core supports composite primary keys: builder.HasKey(o => new { o.Id, o.OrderDate }); — and that's exactly what SQL Server partitioning requires. Your LINQ code stays untouched — everything is transparent.
05
پارتیشنبندی در MySQL — همان ایده، نحوهٔ تعریف متفاوت
Partitioning in MySQL — same idea, different definition syntax
در MySQL، چهار روش پارتیشنبندی یعنی RANGE، LIST، HASH و KEY را داریم، اما نحوهٔ تعریف آنها با SQL Server تفاوت دارد. MySQL همهچیز را داخل خودِ دستور CREATE TABLE تعریف میکند — خبری از اشیای جداگانهای مثل Partition Function و Scheme نیست. یک تفاوت مهم دیگر: از MySQL 8.0 به بعد، فقط InnoDB و NDB از پارتیشنبندی پشتیبانی میکنند ولی MyISAM نه.
In MySQL, we have the four partitioning methods RANGE, LIST, HASH, and KEY, but the way they're defined differs from SQL Server. MySQL defines everything inside the CREATE TABLE statement itself — no separate objects like partition functions and schemes. Another important difference: from MySQL 8.0 onward, only InnoDB and NDB support partitioning — not MyISAM.
mysql — RANGE partitioning
CREATE TABLE orders (
id BIGINT NOT NULL AUTO_INCREMENT,
order_date DATE NOT NULL,
customer_id INT NOT NULL,
total DECIMAL(12,2),
PRIMARY KEY (id, order_date)
)
PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
mysql — RANGE COLUMNS (no function needed)
CREATE TABLE events (
id BIGINT NOT NULL AUTO_INCREMENT,
event_date DATE NOT NULL,
payload JSON,
PRIMARY KEY (id, event_date)
)
PARTITION BY RANGE COLUMNS(event_date) (
PARTITION p2024 VALUES LESS THAN ('2025-01-01'),
PARTITION p2025 VALUES LESS THAN ('2026-01-01'),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
-- COLUMNS variant: use the date directly, no TO_DAYS()
mysql — HASH (your expression)
CREATE TABLE user_sessions (
id INT NOT NULL,
user_id INT NOT NULL,
payload TEXT,
PRIMARY KEY (id, user_id)
)
PARTITION BY HASH(user_id)
PARTITIONS 8;
-- user_id % 8 picks the bucket
mysql — KEY (MySQL's own hash)
CREATE TABLE api_logs (
trace_id VARCHAR(36) NOT NULL,
ts DATETIME NOT NULL,
body TEXT,
PRIMARY KEY (trace_id, ts)
)
PARTITION BY KEY(trace_id)
PARTITIONS 16;
-- works on non-integer columns; MySQL hashes internally
mysql — subpartitioning (partition within partition)
CREATE TABLE sales (
id BIGINT NOT NULL AUTO_INCREMENT,
region VARCHAR(10) NOT NULL,
sale_date DATE NOT NULL,
amount DECIMAL(12,2),
PRIMARY KEY (id, region, sale_date)
)
PARTITION BY LIST COLUMNS(region)
SUBPARTITION BY HASH (YEAR(sale_date))
SUBPARTITIONS 4 (
PARTITION pAMER VALUES IN ('US', 'CA', 'BR'),
PARTITION pEMEA VALUES IN ('DE', 'FR', 'UK'),
PARTITION pAPAC VALUES IN ('JP', 'IN', 'AU')
);
-- first layer: LIST by region
-- second layer: HASH on year within each region
| تفاوتهای کلیدی با SQL ServerKey differences vs SQL Server |
SQL Server |
MySQL |
| محل تعریفDefinition location |
دو شیء جدا (تابع + طرح) و سپس جدولTwo separate objects (function + scheme), then the table |
مستقیم داخل CREATE TABLEInline inside CREATE TABLE |
| انواع پارتیشنPartition types |
RANGE |
RANGE · LIST · HASH · KEY |
| SubpartitioningSubpartitioning |
پشتیبانی نمیشودNot supported |
پشتیبانی میشودSupported |
| کلید یکتا / PKUnique key / PK |
باید شامل ستون پارتیشن باشدMust include the partition column |
همینطور — سختگیرانهSame — strictly enforced |
| Foreign KeyForeign keys |
پشتیبانی میشودSupported |
روی جدول پارتیشنشده پشتیبانی نمیشودNot supported on partitioned tables |
| موتورهای پشتیبانیشدهSupported engines |
همه (SQL Server فقط یک موتور دارد)All (SQL Server only has one engine) |
فقط InnoDB و NDB (از 8.0 به بعد)InnoDB and NDB only (since 8.0) |
| دیدن پارتیشنهای استفادهشدهSee which partitions were used |
Actual Partition Count در پلن اجراin the plan |
ستون partitions در خروجی EXPLAINthe partitions column in EXPLAIN |
.NET
اگر از MySQL با EF Core استفاده میکنید (مثلاً با Pomelo.EntityFrameworkCore.MySql)، پارتیشنبندی برای کد شما کاملاً شفاف است. فقط دو نکته را جدی بگیرید: ۱) محدودیت Foreign Key — اگر مدل شما به FKهای زیادی متکی است، قبل از پارتیشنبندی در MySQL دو بار فکر کنید. ۲) فقط از InnoDB استفاده کنید؛ MyISAM از نسخهٔ ۸٫۰ به بعد دیگر پشتیبانی نمیشود.
If you're using MySQL with EF Core (say, with Pomelo.EntityFrameworkCore.MySql), partitioning is completely transparent to your code. Just keep two things in mind: 1) the foreign-key limitation — if your model relies heavily on FKs, think twice before partitioning in MySQL. 2) use InnoDB only; MyISAM is no longer supported from version 8.0 onward.