高性能MySql进化论(十二):Mysql中分区表的使用总结

本文深入探讨了MySQL中不同类型的分区表策略,包括RANGE、LIST、HASH和KEY分区,旨在提高处理大容量数据时的查询性能。通过详细分析每种分区类型的特点、适用场景及限制,提供了在数据量巨大时有效规避陷阱、优化查询语句的实用建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当数据量非常大时(表的容量到达GB或者是TB),如果仍然采用索引的方式来优化查询,由于索引本生的消耗以及大量的索引碎片的产生,查询的过程会导致大量的随机I/O的产生,在这种场景下除非可以很好的利用覆盖索引,否则由于在查询的过程中需要根据索引回数据表查询,会导致性能受到很大的影响,这时可以考虑通过分区表的策略来提高查询的性能。

 不同的数据库管理系统对分区的实现可能有所区别,本文主要以MYSQL为基础

1        分区的类型

1.1RANGE分区

按照RANGE分区的表是通过如下一种方式进行分区的,每个分区包含那些分区表达式的值位于一个给定的连续区间内的行。这些区间要连续且不能相互重叠,使用VALUES LESS THAN操作符来进行定义

假定你想基于每个雇员离开公司的年份来分割表,也就是说,YEAR(separated)的值。实现这种分区模式的CREATE TABLE 语句的一个例子如下所示。

例如,你可能决定通过添加一个PARTITION BY RANGE子句把这个表分割成4个区间

CREATE TABLE employees (

    id INT NOT NULL,

    fname VARCHAR(30),

    lname VARCHAR(30),

    hired DATE NOT NULL DEFAULT '1970-01-01',

    separated DATE NOT NULL DEFAULT '9999-12-31',

    job_code INT,

    store_id INT

)

PARTITION BY RANGE (YEAR(separated)) (

    PARTITION p0 VALUES LESS THAN (1991),

    PARTITION p1 VALUES LESS THAN (1996),

    PARTITION p2 VALUES LESS THAN (2001),

    PARTITION p3 VALUES LESS THAN MAXVALUE

);


插入一些测试数据后发现P1的数据文件明显增大

mysql> DELIMITER $$

mysql> DROP PROCEDURE IF EXISTS SampleProc$$

Query OK, 0 rows affected (0.00 sec)

mysql> CREATE PROCEDURE SampleProc()

    -> BEGIN

    ->  DECLARE x INT;

    ->  SET x = 1000;

    ->  WHILE x<= 2000 DO

    ->          insert into employees(id,fname,lname,hired,separated,job_code,store_id) values(x,concat('firstname',x),concat('ai',x),'1994-01-01','1995-01-01',10,20);

    ->          SET  x = x + 1;

    ->  END WHILE;

    -> END$$

Query OK, 0 rows affected (0.00 sec)

mysql> call SampleProc() $$

Query OK, 1 row affected (22.55 sec)

mysql> delimiter ;



RANGE分区在如下场合特别有用:

·             当需要删除“旧的”数据时。如果你使用上面最近的那个例子给出的分区方案,你只需简单地使用 “ALTER TABLEemployees DROP PARTITION p0;”来删除所有在1991年前就已经停止工作的雇员相对应的所有行。(更多信息请参见13.1.2节,“ALTER TABLE语法18.3节,分区管理)。对于有大量行的表,这比运行一个如“DELETE FROM employees WHERE YEAR(separated) <= 1990;”这样的一个DELETE查询要有效得多。

·             想要使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列。

·             经常运行直接依赖于用于分割表的列的查询。例如,当执行一个如“SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BYstore_id;”这样的查询时,MySQL可以很迅速地确定只有分区p2需要扫描,这是因为余下的分区不可能包含有符合该WHERE子句的任何记录。

·             分区表达式可以是MySQL 中有效的任何函数或其他表达式,只要它们返回一个既非常数、也非随机数的整数

1.2LIST分区

MySQL中的LIST分区在很多方面类似于RANGE分区。和按照RANGE分区一样,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。LIST分区通过使用“PARTITION BY LIST(expr)”来实现,其中“expr” 是某列值或一个基于某个列值、并返回一个整数值的表达式,然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表。

假定有20个音像店,分布在4个有经销权的地区,如下表所示:

地区

商店ID 号

北区

3, 5, 6, 9, 17

东区

1, 2, 10, 11, 19, 20

西区

4, 12, 13, 14, 18

中心区

7, 8, 15, 16

要按照属于同一个地区商店的行保存在同一个分区中的方式来分割表,可以使用下面的“CREATETABLE”语句

CREATE TABLE employees (

    id INT NOT NULL,

    fname VARCHAR(30),

    lname VARCHAR(30),

    hired DATE NOT NULL DEFAULT '1970-01-01',

    separated DATE NOT NULL DEFAULT '9999-12-31',

    job_code INT,

    store_id INT

)

PARTITION BY LIST(store_id)

    PARTITION pNorth VALUES IN (3,5,6,9,17),

    PARTITION pEast VALUES IN (1,2,10,11,19,20),

    PARTITION pWest VALUES IN (4,12,13,14,18),

    PARTITION pCentral VALUES IN (7,8,15,16)

);


相关的操作和range 分区类似,但有以下问题需要注意

 

·        在MySQL 5.1中,当使用LIST分区时,有可能只能匹配整数列表。

·        如果试图插入列值(或分区表达式的返回值)不在分区值列表中的一行时,那么“INSERT”查询将失败并报错。例如,假定LIST分区的采用上面的方案,下面的查询将失败:

·        LIST分区没有类似如“VALUESLESS THAN MAXVALUE”这样的包含其他值在内的定义。将要匹配的任何值都必须在值列表中找到

1.3     HASH分区

 

HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中,MySQL 自动完成这些工作,你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。

 

例如,下面的语句创建了一个使用基于“store_id”列进行哈希处理的表,该表被分成了4个分区:

CREATE TABLE employees (

    id INT NOT NULL,

    fname VARCHAR(30),

    lname VARCHAR(30),

    hired DATE NOT NULL DEFAULT '1970-01-01',

    separated DATE NOT NULL DEFAULT '9999-12-31',

    job_code INT,

    store_id INT

)

PARTITION BY HASH(store_id)

PARTITIONS 4;



如果没有包括一个PARTITIONS子句,那么分区的数量将默认为1

 

1.4     KEY分区

KEY进行分区类似于按照HASH分区,除了HASH分区使用的用户定义的表达式,而KEY分区的 哈希函数是由MySQL 服务器提供。MySQL 簇(Cluster)使用函数MD5()来实现KEY分区;对于使用其他存储引擎的表,服务器使用其自己内部的 哈希函数,这些函数是基于与PASSWORD()一样的运算法则。

“CREATETABLE ... PARTITION BY KEY”的语法规则类似于创建一个通过HASH分区的表的规则。它们唯一的区别在于使用的关键字是KEY而不是HASH,并且KEY分区只采用一个或多个列名的一个列表。

通过线性KEY分割一个表也是可能的。下面是一个简单的例子:

CREATE TABLE tk (

    col1 INT NOT NULL,

    col2 CHAR(5),

    col3 DATE

) 

PARTITION BY LINEAR KEY (col1)

PARTITIONS 3;



在KEY分区中使用关键字LINEAR和在HASH分区中使用具有同样的作用,分区的编号是通过2的幂(powers-of-two)算法得到,而不是通过模数算法

1.5     更多的知识

上面列出的仅仅是常用的分区策略的用法,当创建了分区后往往还要对分区进行维护,具体请参见:

http://dev.mysql.com/doc/refman/5.1/zh/partitioning.html#partitioning-hash

 

无论使用何种类型的分区,分区总是在创建时就自动的顺序编号,且从0开始记录,记住这一点非常重要。当有一新行插入到一个分区表中时,就是使用这些分区编号来识别正确的分区。例如,如果你的表使用4个分区,那么这些分区就编号为0, 1, 2, 和3。对于RANGE和LIST分区类型,确认每个分区编号都定义了一个分区,很有必要。对HASH分区,使用的用户函数必须返回一个大于0的整数值。对于KEY分区,这个问题通过MySQL服务器内部使用的 哈希函数自动进行处理。

2        使用分区表的限制

·             一个数据表最多只能有1024个分区

·             在MYSQL5.1 版本中分区表达式的结果必须是整数,在MYSQL5.5分区表达式可以使用列

·             如果分区字段中有主键或者是唯一索引列,则所有的主键或者是唯一索引列必须全部包含进来

·             分区表无法使用外键

·             对于同一个表的各个分区表必须使用相同的存储引擎

·             分区函数有限制,只可以是MySQL 中有效的任何函数或其他表达式,且它们返回一个既非常数、也非随机数的整数

·             某些存储引擎不支持分区

3        使用分区表的“陷阱”

在数据量非常大的时候使用分区表可以使性能有较好的改善,但是前提是必须能有效的规避下面列出的一些陷阱

·        NULL值

MySQL 中的分区在禁止空值(NULL)上没有进行处理,无论它是一个列值还是一个用户定义表达式的值。一般而言,对于NULL,或者是当表达式接收非法值时(e.g. YEAR(‘asdf-12-12’))返回的结果都是NULL,在这种情况下MySQL 把NULL视为0,如果大量的记录存在这种情况,最终会导致大量的记录都集中在一个分区中,也也就违背了分区的初衷。

如果你希望回避这种做法,你应该在设计表时不允许空值;最可能的方法是,通过声明列“NOT NULL”来实现这一点。

·        分区列和索引列不匹配

如果定义的索引列和分区列不匹配,则会导致查询无法进行分区过滤。例如在列a上定义分区,在列b上定义索引,因为每个分区都有独立的索引,所以扫描索引时需要扫描每个分区。

应该避免建立和分区列不匹配的索引,除非查询中包含了可以过滤分区的条件。

 

·        选择分区的成本很高

对于Range 和list类型的分区,每次进行操作时都需要遍历所有的分区条件,以判断相关的记录是属于哪个分区,如果分区的数量很多,会在选择分区上浪费较多的资源

 

为了避免这种情况 可以限制分区的数目(<100),或是选择hash分区

 

·        锁住底层表的成本很高

在查询访问分区表的时候,MYSQL会打开并锁住所有的底层表,该操作时再分区过滤之前发生而且和分区类型无关,会影响所有的分区查询。

 

可以通过批量更新的方式来降低该操作的次数,同时也需要限制分区的数目

 

·        维护成本高

增加/删除分区很快捷,但是重组或者是alter分区的过程类似于alter table,会进行大量的数据复制操作,效率很低。

 

4        优化查询语句

对于分区表的访问,最重要的一点是要在where条件中包含分区列,即使看起来是多余的,只有这样才能过滤不需要的分区,否则会访问所以的分区表。

看一个简单的例子。

Employees表使用store_id作为范围分区的条件,如果不使用store_id作为where条件,会查询所有的分区


Store_id作为where条件时,只查询对应的分区

 

关于where条件中的表达式有几点需要注意

·        单纯的使用分区列

Where条件中分区列必须是未经函数处理的,如果where条件写成where YEAR(store_id), 则分区过滤会失效,且查询时会检查所有的分区。这一点和索引类似

 

·        关联查询

如果分区表是关联操作的第二张表,且关联条件是分区建,则MYSQL只会在对应的分区里进行匹配


<think>嗯,用户现在关心的是在两个不同数据库中对两个表执行事务更新的最佳实践。之前已经讨论过跨数据库事务可能引发的问题,比如分布式事务开销、数据一致性风险等。用户提供的引用内容也强调了事务的ACID特性和隔离性的重要性[^2],特别是在更新多个表时确保数据一致性的关键点[^1]。 用户可能是在实际工作中遇到了跨数据库更新的需求,比如微服务架构下的数据同步,或者分布式系统中的业务操作。ta需要具体的解决方案,而不仅仅是理论风险。深层需求可能是希望找到既保证数据强一致性,又避免系统过于复杂的平衡方案。 从技术角度看,这个问题需要分层解决: 首先考虑是否真的需要跨数据库事务。如果业务允许最终一致性,消息队列方案会更合适,比如引用[4]提到的逻辑解码在PostgreSQL中的应用思路,可以借鉴其变更捕获机制。如果必须强一致,那么分布式事务协调器(如MSDTC)是基础,但要注意其配置复杂度[^5]。 其次在工程实现上,Saga模式值得重点展开。用户引用[2]中提到的"中间事务结果必须隐藏"正是Saga补偿机制要解决的痛点——通过正向操作和逆向补偿的编排,避免让其他事务看到中间状态。这点需要结合具体代码示例说明,比如用.NET的Polly库实现补偿事务的可靠性。 最后是监控治理。用户引用[1]强调的"测试验证"在分布式环境下更为关键,需要入对事务状态的实时跟踪。Prometheus等工具可以监控跨库事务的成功率,而Jaeger的分布式追踪能精确定位超时环节。 用户提供的引用[3]虽然讲MySQL更新流程,但WAL日志机制其实暗示了思路:所有数据库的持久化都依赖日志,跨库事务本质上要协调多个日志流。因此解决方案里可以强调"事务日志协同"这个共性原理。</think>在跨数据库事务更新场景中,最佳实践需根据**一致性要求**和**系统复杂度**分层设计。以下是经过验证的解决方案: --- ### 一、强一致性方案(ACID保证) #### 1. 分布式事务(DTC) ```sql BEGIN DISTRIBUTED TRANSACTION; -- 显式声明分布式事务 UPDATE DB1.dbo.Orders SET Status = 'Paid' WHERE OrderID = 1001; UPDATE DB2.dbo.Inventory SET Stock = Stock - 1 WHERE ProductID = 205; COMMIT TRANSACTION; ``` **关键点**: - 依赖MSDTC服务协调两阶段提交(2PC) - 需确保网络稳定且所有节点可用 - 配置要求: ```powershell # 启用MSDTC服务(管理员权限) Set-Service -Name MSDTC -StartupType Automatic Start-Service MSDTC ``` - **风险**:网络分区可能导致阻塞(默认30秒超时)[^2] #### 2. 链接服务器事务 ```sql BEGIN TRANSACTION; -- 本地数据库操作 UPDATE LocalDB..Products SET Price = Price * 0.9; -- 远程数据库操作 EXEC [LinkedServer].RemoteDB.dbo.sp_UpdateInventory @ProductID=101; COMMIT TRANSACTION; ``` **适用场景**:同实例不同数据库或配置了链接服务器的跨实例操作 --- ### 二、最终一致性方案(高可用首选) #### 1. Saga模式 ```mermaid sequenceDiagram participant A as 订单服务(DB1) participant B as 库存服务(DB2) A->>B: 1. 预扣库存请求 B-->>A: 库存预留成功 A->>A: 2. 更新订单状态 A->>B: 3. 提交库存扣减 alt 失败 A->>B: 补偿操作(释放预留库存) end ``` **实现方式**: - **编排式Saga**:中央协调器控制流程 - **协同式Saga**:服务间通过事件触发 - **补偿事务示例**: ```csharp // .NET 使用Polly实现补偿 var policy = Policy.Handle<Exception>() .Fallback(() => InventoryService.RevertDeduction(orderId)); policy.Execute(() => { OrderService.ConfirmOrder(orderId); InventoryService.CommitDeduction(orderId); }); ``` #### 2. 消息队列保障 ```python # Python + RabbitMQ示例 channel.basic_publish( exchange='', routing_key='order_queue', body=json.dumps({'order_id': 1001, 'action': 'deduct'}), properties=pika.BasicProperties( delivery_mode=2, # 持久化消息 )) # 消费者处理失败时重试或进入死信队列 ``` --- ### 三、混合方案(平衡性能与一致性) #### 1. 本地事务+异步复制 ```sql /* DB1执行 */ BEGIN TRANSACTION; UPDATE Orders SET Status = 'Paid' WHERE OrderID = 1001; INSERT INTO SyncQueue (Operation, TargetDB, Payload) VALUES ('UPDATE', 'DB2', '{"ProductID":205,"Qty":-1}'); COMMIT TRANSACTION; /* 后台作业读取SyncQueue同步到DB2 */ ``` #### 2. CDC(变更数据捕获) ```sql -- 启用CDC EXEC sys.sp_cdc_enable_db; EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'Orders', @role_name = NULL; ``` **优势**: - 通过捕获日志异步同步 - 避免对业务代码侵入(如SQL Server Always On可用性组) --- ### 关键决策因素 | 维度 | 分布式事务 | Saga模式 | 异步复制 | |--------------------|---------------------------|-----------------------|----------------| | **一致性** | 强一致性(ACID) | 最终一致性 | 最终一致性 | | **吞吐量** | 低(锁竞争高) | 高(无全局锁) | 高 | | **复杂度** | 中(依赖DTC) | 高(需设计补偿) | 低 | | **故障恢复** | 回滚简单 | 需手动处理补偿 | 需幂等设计 | | **适用场景** | 金融交易/票务 | 电商订单/库存 | 日志同步 | > **实践建议**: > - 金融系统首选分布式事务+重试机制 > - 高并发场景用Saga+消息队列(如Kafka事务消息) > - 跨云环境考虑CDC或专用同步工具(如Debezium) --- ### 监控与故障处理 1. **分布式追踪**: ```bash # 使用Jaeger跟踪跨库事务 docker run -d --name jaeger -p 16686:16686 jaegertracing/all-in-one ``` 2. **超时控制**: ```sql SET LOCK_TIMEOUT 5000; -- 设置5秒锁超时 BEGIN TRY BEGIN DISTRIBUTED TRANSACTION; -- ... END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK; THROW; -- SQL Server 2012+ END CATCH ``` 3. **补偿审计表**: ```sql CREATE TABLE TransactionAudit ( TxID UNIQUEIDENTIFIER PRIMARY KEY, Status VARCHAR(20) CHECK(Status IN ('Pending','Completed','Failed')), CompensationScript NVARCHAR(MAX) -- 存储回滚SQL ); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值