Skip to content

Commit 6a52d12

Browse files
authored
Merge pull request #32 from mingjie-zhang1998/feature/table_compute_tool
1. 新增表碎片及大表数据统计;2. 当查询系统表时,创建账号需要赋多个数据库的权限
2 parents f0e4a46 + cf316ee commit 6a52d12

File tree

5 files changed

+156
-5
lines changed

5 files changed

+156
-5
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ Add the following configuration to the MCP client configuration file:
139139
* `explain_sql`: Execute sql `explain` and return sql result.
140140
* `show_engine_innodb_status`: Execute sql `show engine innodb status` and return sql result.
141141
* `show_create_table`: Execute sql `show create table` and return sql result.
142+
* `show_largest_table`: Query the top few tables with the highest space occupancy.
143+
* `show_largest_table_fragment`: Query the tables with the largest table fragments.
142144
* `query_sql`: Execute read-only sql and return sql result.
143145

144146
### Toolsets

README_CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ git clone https://github.com/aliyun/alibabacloud-rds-openapi-mcp-server.git
136136
* `explain_sql`:执行 SQL `explain` 命令并返回 SQL 执行计划结果。
137137
* `show_engine_innodb_status`:执行 SQL `show engine innodb status` 命令并返回 SQL 执行结果。
138138
* `show_create_table`:执行 SQL `show create table` 命令并返回 SQL 执行结果。
139+
* `show_largest_table`: 查询空间占用率最高的前几张表。
140+
* `show_largest_table_fragment`: 查询空间碎片率最高的前几张表。
139141
* `query_sql`:执行只读 SQL 语句并返回 SQL 执行结果。
140142

141143

src/alibabacloud_rds_openapi_mcp_server/db_service.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ class DBService:
4949
def __init__(self,
5050
region_id,
5151
instance_id,
52-
database=None, ):
52+
database=None,
53+
privilege_databases=None):
5354
self.instance_id = instance_id
5455
self.database = database
5556
self.region_id = region_id
57+
self.privilege_databases = privilege_databases
5658

5759
self.__db_type = None
5860
self.__account_name, self.__account_password = get_rds_account()
@@ -126,11 +128,22 @@ def _create_temp_account(self):
126128
self.__client.create_account(request)
127129

128130
def _grant_privilege(self):
131+
if self.privilege_databases:
132+
dbname = self.privilege_databases
133+
cnt = len(self.privilege_databases.split(','))
134+
if self.db_type.lower() in ('mysql', 'postgresql'):
135+
privilege = ",".join(['ReadOnly' for i in range(cnt)])
136+
else:
137+
privilege = ",".join(['DBOwner' for i in range(cnt)])
138+
else:
139+
dbname = self.database
140+
privilege = "ReadOnly" if self.db_type.lower() in ('mysql', 'postgresql') else "DBOwner"
141+
129142
req = rds_20140815_models.GrantAccountPrivilegeRequest(
130143
dbinstance_id=self.instance_id,
131144
account_name=self.account_name,
132-
dbname=self.database,
133-
account_privilege="ReadOnly" if self.db_type.lower() in ('mysql', 'postgresql') else "DBOwner"
145+
dbname=dbname,
146+
account_privilege=privilege
134147
)
135148
self.__client.grant_account_privilege(req)
136149

src/alibabacloud_rds_openapi_mcp_server/server.py

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
import time
1717
from datetime import datetime
1818
from typing import Dict, Any, List, Optional
19+
from pydantic import Field
1920

2021
from alibabacloud_bssopenapi20171214 import models as bss_open_api_20171214_models
2122
from alibabacloud_openapi_util.client import Client as OpenApiUtilClient
23+
from alibabacloud_rds20140815.client import Client as RdsClient
2224
from alibabacloud_rds20140815 import models as rds_20140815_models
2325
from alibabacloud_tea_openapi import models as open_api_models
2426
from alibabacloud_tea_util import models as util_models
@@ -1642,11 +1644,111 @@ async def query_sql(
16421644
the sql result.
16431645
"""
16441646
try:
1645-
async with DBService(region_id, dbinstance_id, db_name) as service:
1647+
if db_name == 'information_schema':
1648+
client = get_rds_client(region_id)
1649+
databases = _get_db_instance_databases_str(client, region_id, dbinstance_id)
1650+
else:
1651+
databases = None
1652+
async with DBService(region_id, dbinstance_id, db_name, databases) as service:
16461653
return await service.execute_sql(sql=sql)
16471654
except Exception as e:
16481655
logger.error(f"Error occurred: {str(e)}")
16491656
raise e
1657+
1658+
@mcp.tool(annotations=READ_ONLY_TOOL,
1659+
description="Query the top few tables with the highest space occupancy")
1660+
async def show_largest_table(
1661+
region_id: str = Field(description="The region ID of the RDS instance."),
1662+
dbinstance_id: str = Field(description="The ID of the RDS instance."),
1663+
topK: int = Field(default=5, description="To display the number of the top few largest tables")
1664+
) -> str:
1665+
"""
1666+
Returns:
1667+
The data table with the largest storage space.
1668+
"""
1669+
try:
1670+
client = get_rds_client(region_id)
1671+
request = rds_20140815_models.DescribeDBInstanceAttributeRequest(dbinstance_id=dbinstance_id)
1672+
response = client.describe_dbinstance_attribute(request)
1673+
result = response.body.to_map()
1674+
1675+
db_type = result['Items']['DBInstanceAttribute'][0]['Engine']
1676+
db_version = result['Items']['DBInstanceAttribute'][0]['EngineVersion']
1677+
if db_type.lower() != 'mysql' and db_version not in ["5.6", "5.7", "8.0"]:
1678+
return f"Unsupported db version {db_version}."
1679+
1680+
db_name = 'information_schema'
1681+
databases = _get_db_instance_databases_str(client, region_id, dbinstance_id)
1682+
# 构建SQL
1683+
# 限制展示最大表数量的上限
1684+
topK = min(topK, 100)
1685+
base_query = f"""
1686+
SELECT
1687+
table_schema AS '数据库',
1688+
table_name AS '表名',
1689+
ROUND((data_length + index_length) / 1024 / 1024, 2) AS '总大小(MB)',
1690+
ROUND(INDEX_LENGTH / 1024 / 1024, 2) AS '索引大小(MB)',
1691+
table_rows AS '行数'
1692+
FROM information_schema.TABLES
1693+
ORDER BY (data_length + index_length) DESC
1694+
Limit {topK};
1695+
"""
1696+
1697+
async with DBService(region_id, dbinstance_id, db_name, databases) as service:
1698+
return await service.execute_sql(sql=base_query)
1699+
except Exception as e:
1700+
logger.exception("show largest table failed.")
1701+
logger.error(f"Error occurred: {str(e)}")
1702+
raise e
1703+
1704+
1705+
@mcp.tool(annotations=READ_ONLY_TOOL,
1706+
description="Query the tables with the largest table fragments")
1707+
async def show_largest_table_fragment(
1708+
region_id: str = Field(description="The region ID of the RDS instance."),
1709+
dbinstance_id: str = Field(description="The ID of the RDS instance."),
1710+
topK: int = Field(default=5, description="To display the number of the top few largest tables")
1711+
) -> str:
1712+
"""
1713+
Returns:
1714+
The largest fragmented data table.
1715+
"""
1716+
try:
1717+
client = get_rds_client(region_id)
1718+
request = rds_20140815_models.DescribeDBInstanceAttributeRequest(dbinstance_id=dbinstance_id)
1719+
response = client.describe_dbinstance_attribute(request)
1720+
result = response.body.to_map()
1721+
1722+
db_type = result['Items']['DBInstanceAttribute'][0]['Engine']
1723+
db_version = result['Items']['DBInstanceAttribute'][0]['EngineVersion']
1724+
if db_type.lower() != 'mysql' and db_version not in ["5.6", "5.7", "8.0"]:
1725+
return f"Unsupported db version {db_version}."
1726+
1727+
db_name = 'information_schema'
1728+
databases = _get_db_instance_databases_str(client, region_id, dbinstance_id)
1729+
# 构建SQL
1730+
# 限制展示最大表数量的上限
1731+
topK = min(topK, 100)
1732+
base_query = f"""
1733+
SELECT
1734+
ENGINE,
1735+
TABLE_SCHEMA,
1736+
TABLE_NAME,
1737+
ROUND(DATA_LENGTH / 1024 / 1024,2) AS '总大小(MB)',
1738+
ROUND(INDEX_LENGTH / 1024 / 1024,2) AS '索引大小(MB)',
1739+
ROUND(DATA_FREE / 1024 / 1024,2) AS '碎片大小(MB)'
1740+
FROM information_schema.TABLES
1741+
WHERE DATA_FREE > 0
1742+
ORDER BY DATA_FREE DESC
1743+
Limit {topK};
1744+
"""
1745+
1746+
async with DBService(region_id, dbinstance_id, db_name, databases) as service:
1747+
return await service.execute_sql(sql=base_query)
1748+
except Exception as e:
1749+
logger.exception("show largest table failed.")
1750+
logger.error(f"Error occurred: {str(e)}")
1751+
raise e
16501752

16511753
async def health_check(request: Request) -> JSONResponse:
16521754
"""Health check endpoint for container health monitoring and load balancer probes."""
@@ -1763,6 +1865,24 @@ def _parse_groups_from_source(source: str | None) -> List[str]:
17631865
expanded_groups.append(g_to_add)
17641866
return expanded_groups or [DEFAULT_TOOL_GROUP]
17651867

1868+
def _get_db_instance_databases_str(
1869+
client: RdsClient,
1870+
region_id: str,
1871+
db_instance_id: str
1872+
) -> str:
1873+
try:
1874+
client = get_rds_client(region_id)
1875+
request = rds_20140815_models.DescribeDatabasesRequest(
1876+
dbinstance_id=db_instance_id
1877+
)
1878+
response = client.describe_databases(request)
1879+
databases_info = response.body.databases.database
1880+
1881+
result = ",".join([database.dbname for database in databases_info])
1882+
return result
1883+
except Exception as e:
1884+
raise e
1885+
17661886

17671887
if __name__ == "__main__":
17681888
parser = argparse.ArgumentParser()

src/alibabacloud_rds_openapi_mcp_server/utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,21 @@
5656
"DiskUsage": ["disk_usage"],
5757
"IOPSUsage": ["data_iops_usage"],
5858
"IOBytesPS": ["data_io_bytes_ps"],
59-
"MdlLockSession": ["mdl_lock_session"]
59+
"MdlLockSession": ["mdl_lock_session"],
60+
"RelayLogSize": ["relay_log_size"],
61+
"UndoLogSize": ["undolog_size"],
62+
"RedoLog_Size": ["redolog_size"],
63+
"TempFileSize": ["temp_file_size"],
64+
"InsSize": ["ins_size"],
65+
"SysDataSize": ["sys_data_size"],
66+
"GeneralLogSize": ["general_log_size"],
67+
"SlowLogSize": ["slow_log_size"],
68+
"BinlogSize": ["binlog_size"],
69+
"UserDataSize": ["user_data_size"],
70+
"InnodbRowsInsert":["Innodb_rows_inserted_ps"],
71+
"MemCpuUsage": ["cpu_usage"],
72+
"QPS": ["qps"],
73+
"SLowSQL": ["slow_sql"]
6074
}
6175
}
6276

0 commit comments

Comments
 (0)