greatsql教程FG020-GreatSQL分表分库实战
内容简介
本教程详细介绍GreatSQL数据库的分表分库技术,包括分表分库的概念、实现方式、最佳实践等内容。风哥教程参考GreatSQL官方文档分表分库指南,帮助读者掌握分表分库的设计和实现。
分表分库是解决数据库性能瓶颈的重要手段,通过将大表拆分为小表,将数据分布到多个数据库中,可以显著提高系统性能和扩展性。本教程将从基础概念入手,逐步深入到实战案例和最佳实践。
目录大纲
Part01-基础概念与理论知识
1.1 分表分库概述
分表分库是一种数据库水平扩展技术,通过将数据分散存储到多个表或多个数据库中,以提高系统性能和扩展性。分表分库主要包括:
- 分表:将一个大表拆分为多个小表
- 分库:将数据分散到多个数据库中
1.2 分表分库的目的
分表分库的主要目的是:
- 提高查询性能
- 增加系统扩展性
- 减少单表数据量
- 提高并发处理能力
- 便于数据管理和维护
1.3 分表分库的类型
分表分库的主要类型包括:
- 水平分表:按行拆分,将同一表的数据分散到多个表中
- 垂直分表:按列拆分,将表的不同列分散到不同表中
- 水平分库:按行拆分,将数据分散到多个数据库中
- 垂直分库:按业务拆分,将不同业务的数据分散到不同数据库中
Part02-生产环境规划与建议
2.1 分表分库策略规划
风哥提示:分表分库策略应根据业务需求、数据量和查询模式进行规划,确保系统性能和可维护性。
分表分库策略规划建议:
- 数据量评估:评估当前数据量和增长趋势
- 查询模式分析:分析常见查询模式和访问频率
- 分片策略选择:根据查询模式选择合适的分片策略
- 分片数量确定:根据数据量和性能需求确定分片数量
- 扩容方案设计:设计未来的扩容方案
2.2 分片键选择
分片键选择建议:
- 选择频繁查询的字段作为分片键
- 选择基数大的字段作为分片键
- 选择分布均匀的字段作为分片键
- 避免选择经常更新的字段作为分片键
- 考虑业务逻辑,选择有业务意义的字段作为分片键
2.3 性能优化建议
分表分库性能优化建议:
- 合理设计分片策略,避免数据倾斜
- 使用连接池管理数据库连接
- 实施缓存机制,减少数据库访问
- 优化查询语句,减少跨分片查询
- 定期清理历史数据,保持表大小合理
更多视频教程www.fgedu.net.cn
Part03-生产环境项目实施方案
3.1 分表分库实施方案
分表分库实施步骤:
- 确定分表分库策略
- 选择分片键
- 创建分片表或分片库
- 编写数据迁移脚本
- 执行数据迁移
- 修改应用代码,适配分表分库
- 测试系统性能
- 上线运行
3.2 数据迁移方案
数据迁移方案:
- 全量迁移:一次性迁移所有数据
- 增量迁移:先迁移历史数据,再迁移增量数据
- 双写迁移:同时向旧表和新表写入数据,验证后切换
3.3 应用适配方案
应用适配方案:
- 修改数据访问层,支持分表分库
- 使用分片中间件,如Sharding-JDBC
- 调整业务逻辑,避免跨分片查询
- 优化查询语句,提高性能
Part04-生产案例与实战讲解
4.1 水平分表实战
# 创建水平分表
# 创建基础表结构 CREATE TABLE fgedudb.fgedu_orders_base ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(50) NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, status INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
# 创建分表 CREATE TABLE fgedudb.fgedu_orders_202601 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202602 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202603 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202604 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202605 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202606 LIKE fgedudb.fgedu_orders_base;
# 创建基础表结构 CREATE TABLE fgedudb.fgedu_orders_base ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(50) NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, status INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
# 创建分表 CREATE TABLE fgedudb.fgedu_orders_202601 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202602 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202603 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202604 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202605 LIKE fgedudb.fgedu_orders_base; CREATE TABLE fgedudb.fgedu_orders_202606 LIKE fgedudb.fgedu_orders_base;
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
# 创建存储过程,根据时间自动分表 DELIMITER // CREATE PROCEDURE fgedudb.insert_order( IN p_order_no VARCHAR(50), IN p_user_id INT, IN p_amount DECIMAL(10,2), IN p_status INT ) BEGIN DECLARE table_name VARCHAR(50); DECLARE current_month VARCHAR(6); — 获取当前月份 SET current_month = DATE_FORMAT(NOW(), ‘%Y%m’); — 构建表名 SET table_name = CONCAT(‘fgedu_orders_’, current_month); — 检查表是否存在 SET @check_sql = CONCAT(‘CREATE TABLE IF NOT EXISTS fgedudb.’, table_name, ‘ LIKE fgedudb.fgedu_orders_base’); PREPARE stmt FROM @check_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; — 插入数据 SET @insert_sql = CONCAT(‘INSERT INTO fgedudb.’, table_name, ‘ (order_no, user_id, amount, status) VALUES (?, ?, ?, ?)’); PREPARE stmt FROM @insert_sql; SET @order_no = p_order_no; SET @user_id = p_user_id; SET @amount = p_amount; SET @status = p_status; EXECUTE stmt USING @order_no, @user_id, @amount, @status; DEALLOCATE PREPARE stmt; END // DELIMITER ;
Query OK, 0 rows affected (0.01 sec)
学习交流加群风哥微信: itpux-com
# 测试分表插入 CALL fgedudb.insert_order(‘ORD20260409001’, 1, 100.00, 1); CALL fgedudb.insert_order(‘ORD20260409002’, 2, 200.00, 1); CALL fgedudb.insert_order(‘ORD20260409003’, 3, 150.00, 1);
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
# 验证数据插入 SELECT * FROM fgedudb.fgedu_orders_202604;
+—-+—————+———+——–+——–+———————+
| id | order_no | user_id | amount | status | created_at |
+—-+—————+———+——–+——–+———————+
| 1 | ORD20260409001 | 1 | 100.00 | 1 | 2026-04-09 20:00:00 |
| 2 | ORD20260409002 | 2 | 200.00 | 1 | 2026-04-09 20:00:00 |
| 3 | ORD20260409003 | 3 | 150.00 | 1 | 2026-04-09 20:00:00 |
+—-+—————+———+——–+——–+———————+
| id | order_no | user_id | amount | status | created_at |
+—-+—————+———+——–+——–+———————+
| 1 | ORD20260409001 | 1 | 100.00 | 1 | 2026-04-09 20:00:00 |
| 2 | ORD20260409002 | 2 | 200.00 | 1 | 2026-04-09 20:00:00 |
| 3 | ORD20260409003 | 3 | 150.00 | 1 | 2026-04-09 20:00:00 |
+—-+—————+———+——–+——–+———————+
4.2 垂直分表实战
# 创建垂直分表
# 创建用户基本信息表 CREATE TABLE fgedudb.fgedu_users_basic ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
# 创建用户扩展信息表 CREATE TABLE fgedudb.fgedu_users_extend ( user_id INT PRIMARY KEY, real_name VARCHAR(50), phone VARCHAR(20), address VARCHAR(255), birthday DATE, FOREIGN KEY (user_id) REFERENCES fgedudb.fgedu_users_basic(id) );
# 创建用户基本信息表 CREATE TABLE fgedudb.fgedu_users_basic ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
# 创建用户扩展信息表 CREATE TABLE fgedudb.fgedu_users_extend ( user_id INT PRIMARY KEY, real_name VARCHAR(50), phone VARCHAR(20), address VARCHAR(255), birthday DATE, FOREIGN KEY (user_id) REFERENCES fgedudb.fgedu_users_basic(id) );
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
# 插入用户数据 INSERT INTO fgedudb.fgedu_users_basic (username, password, email) VALUES (‘fgedu_user1’, ‘password1’, ‘fgedu_user1@fgedu.net.cn’), (‘fgedu_user2’, ‘password2’, ‘fgedu_user2@fgedu.net.cn’), (‘fgedu_user3’, ‘password3’, ‘fgedu_user3@fgedu.net.cn’); INSERT INTO fgedudb.fgedu_users_extend (user_id, real_name, phone, address, birthday) VALUES (1, ‘张三’, ‘13800138001’, ‘北京市朝阳区’, ‘1990-01-01’), (2, ‘李四’, ‘13900139001’, ‘上海市浦东新区’, ‘1991-02-02’), (3, ‘王五’, ‘13700137001’, ‘广州市天河区’, ‘1992-03-03’);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
Records: 3 Duplicates: 0 Warnings: 0
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
学习交流加群风哥QQ113257174
# 查询用户信息 SELECT b.id, b.username, b.email, e.real_name, e.phone, e.address FROM fgedudb.fgedu_users_basic b LEFT JOIN fgedudb.fgedu_users_extend e ON b.id = e.user_id WHERE b.id = 1;
+—-+———-+——————+———–+————-+——————+
| id | username | email | real_name | phone | address |
+—-+———-+——————+———–+————-+——————+
| 1 | fgedu_user1 | fgedu_user1@fgedu.net.cn | 张三 | 13800138001 | 北京市朝阳区 |
+—-+———-+——————+———–+————-+——————+
| id | username | email | real_name | phone | address |
+—-+———-+——————+———–+————-+——————+
| 1 | fgedu_user1 | fgedu_user1@fgedu.net.cn | 张三 | 13800138001 | 北京市朝阳区 |
+—-+———-+——————+———–+————-+——————+
4.3 分库实战
# 创建分库 CREATE DATABASE fgedudb_shard1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE fgedudb_shard2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE fgedudb_shard3 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Query OK, 1 row affected (0.01 sec)
Query OK, 1 row affected (0.01 sec)
Query OK, 1 row affected (0.01 sec)
Query OK, 1 row affected (0.01 sec)
Query OK, 1 row affected (0.01 sec)
# 在每个分库中创建表 CREATE TABLE fgedudb_shard1.fgedu_users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ); CREATE TABLE fgedudb_shard2.fgedu_users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ); CREATE TABLE fgedudb_shard3.fgedu_users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL );
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
# 创建分库存储过程 DELIMITER // CREATE PROCEDURE fgedudb.insert_user_shard( IN p_username VARCHAR(50), IN p_email VARCHAR(100) ) BEGIN DECLARE shard_id INT; DECLARE db_name VARCHAR(50); — 根据用户名哈希计算分片ID SET shard_id = MOD(CRC32(p_username), 3) + 1; SET db_name = CONCAT(‘fgedudb_shard’, shard_id); — 插入数据 SET @insert_sql = CONCAT(‘INSERT INTO ‘, db_name, ‘.fgedu_users (username, email) VALUES (?, ?)’); PREPARE stmt FROM @insert_sql; SET @username = p_username; SET @email = p_email; EXECUTE stmt USING @username, @email; DEALLOCATE PREPARE stmt; END // DELIMITER ;
Query OK, 0 rows affected (0.01 sec)
# 测试分库插入 CALL fgedudb.insert_user_shard(‘fgedu_user1’, ‘fgedu_user1@fgedu.net.cn’); CALL fgedudb.insert_user_shard(‘fgedu_user2’, ‘fgedu_user2@fgedu.net.cn’); CALL fgedudb.insert_user_shard(‘fgedu_user3’, ‘fgedu_user3@fgedu.net.cn’); CALL fgedudb.insert_user_shard(‘fgedu_user4’, ‘fgedu_user4@fgedu.net.cn’); CALL fgedudb.insert_user_shard(‘fgedu_user5’, ‘fgedu_user5@fgedu.net.cn’); CALL fgedudb.insert_user_shard(‘fgedu_user6’, ‘fgedu_user6@fgedu.net.cn’);
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
# 验证数据分布 SELECT * FROM fgedudb_shard1.fgedu_users; SELECT * FROM fgedudb_shard2.fgedu_users; SELECT * FROM fgedudb_shard3.fgedu_users;
+—-+———-+——————+
| id | username | email |
+—-+———-+——————+
| 1 | fgedu_user3 | fgedu_user3@fgedu.net.cn |
| 2 | fgedu_user6 | fgedu_user6@fgedu.net.cn |
+—-+———-+——————+
+—-+———-+——————+
| id | username | email |
+—-+———-+——————+
| 1 | fgedu_user1 | fgedu_user1@fgedu.net.cn |
| 2 | fgedu_user4 | fgedu_user4@fgedu.net.cn |
+—-+———-+——————+
+—-+———-+——————+
| id | username | email |
+—-+———-+——————+
| 1 | fgedu_user2 | fgedu_user2@fgedu.net.cn |
| 2 | fgedu_user5 | fgedu_user5@fgedu.net.cn |
+—-+———-+——————+
| id | username | email |
+—-+———-+——————+
| 1 | fgedu_user3 | fgedu_user3@fgedu.net.cn |
| 2 | fgedu_user6 | fgedu_user6@fgedu.net.cn |
+—-+———-+——————+
+—-+———-+——————+
| id | username | email |
+—-+———-+——————+
| 1 | fgedu_user1 | fgedu_user1@fgedu.net.cn |
| 2 | fgedu_user4 | fgedu_user4@fgedu.net.cn |
+—-+———-+——————+
+—-+———-+——————+
| id | username | email |
+—-+———-+——————+
| 1 | fgedu_user2 | fgedu_user2@fgedu.net.cn |
| 2 | fgedu_user5 | fgedu_user5@fgedu.net.cn |
+—-+———-+——————+
Part05-风哥经验总结与分享
5.1 分表分库最佳实践
- 合理选择分片策略:根据业务需求和数据特点选择合适的分片策略
- 优化分片键:选择合适的分片键,确保数据分布均匀
- 避免跨分片查询:尽量减少跨分片查询,提高性能
- 使用中间件:使用分片中间件,简化应用开发
- 定期维护:定期清理历史数据,保持表大小合理
- 监控和调优:监控分片性能,及时调优
- 考虑扩容:设计时考虑未来的扩容需求
5.2 常见问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 数据倾斜 | 选择分布均匀的分片键,定期重新分片 |
| 跨分片查询性能差 | 优化查询语句,使用缓存,避免跨分片查询 |
| 扩容困难 | 设计时考虑扩容需求,使用一致性哈希等算法 |
| 事务处理复杂 | 使用分布式事务,或设计为最终一致性 |
| 数据迁移困难 | 使用专业迁移工具,制定详细的迁移计划 |
更多学习教程公众号风哥教程itpux_com
5.3 性能优化技巧
# 创建分表分库管理脚本
cat > /greatsql/scripts/sharding_management.sh << 'EOF'
#!/bin/bash # sharding_management.sh
# from:www.itpux.com.qq113257174.wx:itpux-com
# web: http://www.fgedu.net.cn
echo “=== GreatSQL Sharding Management ===” echo “Date: $(date)” echo “”
# 检查分表状态
echo “1. Checking sharding tables…” mysql -u root -pFGedu123456! -e “SHOW TABLES LIKE ‘fgedu_orders_%’;”
# 检查分库状态
echo “2. Checking sharding databases…” mysql -u root -pFGedu123456! -e “SHOW DATABASES LIKE ‘fgedudb_shard%’;”
# 检查分表数据量
echo “3. Checking sharding table sizes…” for table in $(mysql -u root -pFGedu123456! -e “SHOW TABLES LIKE ‘fgedu_orders_%'” -s); do size=$(mysql -u root -pFGedu123456! -e “SELECT ROUND(data_length + index_length) FROM information_schema.tables WHERE table_schema=’fgedudb’ AND table_name=’$table'” -s) echo “$table: $size bytes” done
# 检查分库数据量
echo “4. Checking sharding database sizes…” for db in $(mysql -u root -pFGedu123456! -e “SHOW DATABASES LIKE ‘fgedudb_shard%'” -s); do size=$(mysql -u root -pFGedu123456! -e “SELECT ROUND(SUM(data_length + index_length)) FROM information_schema.tables WHERE table_schema=’$db'” -s) echo “$db: $size bytes” done echo “” echo “Sharding management completed!” EOF
# 设置脚本权限
chmod +x /greatsql/scripts/sharding_management.sh
cat > /greatsql/scripts/sharding_management.sh << 'EOF'
#!/bin/bash # sharding_management.sh
# from:www.itpux.com.qq113257174.wx:itpux-com
# web: http://www.fgedu.net.cn
echo “=== GreatSQL Sharding Management ===” echo “Date: $(date)” echo “”
# 检查分表状态
echo “1. Checking sharding tables…” mysql -u root -pFGedu123456! -e “SHOW TABLES LIKE ‘fgedu_orders_%’;”
# 检查分库状态
echo “2. Checking sharding databases…” mysql -u root -pFGedu123456! -e “SHOW DATABASES LIKE ‘fgedudb_shard%’;”
# 检查分表数据量
echo “3. Checking sharding table sizes…” for table in $(mysql -u root -pFGedu123456! -e “SHOW TABLES LIKE ‘fgedu_orders_%'” -s); do size=$(mysql -u root -pFGedu123456! -e “SELECT ROUND(data_length + index_length) FROM information_schema.tables WHERE table_schema=’fgedudb’ AND table_name=’$table'” -s) echo “$table: $size bytes” done
# 检查分库数据量
echo “4. Checking sharding database sizes…” for db in $(mysql -u root -pFGedu123456! -e “SHOW DATABASES LIKE ‘fgedudb_shard%'” -s); do size=$(mysql -u root -pFGedu123456! -e “SELECT ROUND(SUM(data_length + index_length)) FROM information_schema.tables WHERE table_schema=’$db'” -s) echo “$db: $size bytes” done echo “” echo “Sharding management completed!” EOF
# 设置脚本权限
chmod +x /greatsql/scripts/sharding_management.sh
# 执行分表分库管理脚本 /greatsql/scripts/sharding_management.sh
=== GreatSQL Sharding Management ===
Date: Wed Apr 9 20:00:00 CST 2026
1. Checking sharding tables…
+———————–+
| Tables_in_fgedudb |
+———————–+
| fgedu_orders_202604 |
+———————–+
2. Checking sharding databases…
+——————–+
| Database |
+——————–+
| fgedudb_shard1 |
| fgedudb_shard2 |
| fgedudb_shard3 |
+——————–+
3. Checking sharding table sizes…
fgedu_orders_202604: 16384 bytes
4. Checking sharding database sizes…
fgedudb_shard1: 16384 bytes
fgedudb_shard2: 16384 bytes
fgedudb_shard3: 16384 bytes
Sharding management completed!
Date: Wed Apr 9 20:00:00 CST 2026
1. Checking sharding tables…
+———————–+
| Tables_in_fgedudb |
+———————–+
| fgedu_orders_202604 |
+———————–+
2. Checking sharding databases…
+——————–+
| Database |
+——————–+
| fgedudb_shard1 |
| fgedudb_shard2 |
| fgedudb_shard3 |
+——————–+
3. Checking sharding table sizes…
fgedu_orders_202604: 16384 bytes
4. Checking sharding database sizes…
fgedudb_shard1: 16384 bytes
fgedudb_shard2: 16384 bytes
fgedudb_shard3: 16384 bytes
Sharding management completed!
分表分库中间件推荐
- Sharding-JDBC:轻量级Java分库分表中间件
- MyCAT:开源的分布式数据库中间件
- ProxySQL:MySQL代理,支持读写分离和分库分表
- Vitess:谷歌开源的MySQL集群解决方案
分表分库案例分享
案例背景:某电商平台订单表数据量达到1000万条,查询性能下降明显。
解决方案:
- 采用水平分表,按月份拆分订单表
- 使用存储过程自动创建分表
- 修改应用代码,适配分表逻辑
- 实施后查询性能提升5倍以上
风哥提示:分表分库是解决数据库性能瓶颈的有效手段,但也增加了系统复杂度,需要根据实际情况合理使用。
分表分库实施建议
- 在数据量达到百万级时考虑分表
- 在单库数据量达到千万级时考虑分库
- 选择合适的分片策略和分片键
- 使用中间件简化应用开发
- 定期监控和维护分表分库
- 制定详细的扩容计划
from greatsql视频:www.itpux.com
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
