From 68226041f7c37148fce9bf362a3cdab2e6f3df84 Mon Sep 17 00:00:00 2001 From: "Amias.Q.Li" Date: Thu, 16 Nov 2023 10:24:32 +0800 Subject: [PATCH 1/2] add requirements.tx for dependencies --- README.md | 15 +++++++++++++-- requirements.txt | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 3eaa5d1..f3b0120 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,27 @@ To install this demo on your machine type the following commands after you have This will clone the repository and copy the files to the right path in order to be automatically loaded on MySQL Shell startup. ### macOS / Linux -``` +```shell $ mkdir -p ~/.mysqlsh/plugins $ git clone https://github.com/lefred/mysqlshell-plugins.git ~/.mysqlsh/plugins ``` ### Windows -``` +```shell $ mkdir %AppData%\MySQL\mysqlsh\plugins $ git clone https://github.com/lefred/mysqlshell-plugins.git %AppData%\MySQL\mysqlsh\plugins ``` + +## Dependencies + +```shell +$ sudo mysqlsh --pym pip install -r requirements.txt +``` + +```shell +$ mysqlsh --pym pip install --user -r requirements.txt +``` + ## Missing Modules It might be possible that for some plugins, your are missing some modules. Usually it is ``python3-requests``. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..60edf78 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests~=2.31.0 +prettytable~=3.9.0 \ No newline at end of file From 8b0e1eb9fb868d74036ddc7e8ca3c527f6768e2b Mon Sep 17 00:00:00 2001 From: "Amias.Q.Li" Date: Sat, 18 Nov 2023 21:59:13 +0800 Subject: [PATCH 2/2] Add check common unused indexes in replica set and cluster --- check/index.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++ check/init.py | 1 + requirements.txt | 3 +- 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 check/index.py diff --git a/check/index.py b/check/index.py new file mode 100644 index 0000000..8edc347 --- /dev/null +++ b/check/index.py @@ -0,0 +1,176 @@ +from mysqlsh.plugin_manager import plugin, plugin_function +import mysqlsh + +try: + import pandas as pd + + pandas_present = True +except: + pandas_present = False + mysqlsh.globals.shell.log("WARNING", + "Python module pandas are not present, check.get_unused_indexes() won't work") + + mysqlsh.globals.shell.log("WARNING", + "Try:\n mysqlsh --pym pip install --user pandas") + + +def _query_unused_indexes(session): + if not pandas_present: + print("Python module pandas are not present, check.get_unused_indexes() won't work") + print("Try:\n mysqlsh --py pip install --user pandas") + return + exclueded_schemas = ['information_schema', + 'mysql', + 'performance_schema', + 'sys', + 'mysql_innodb_cluster_metadata'] + + filter_string = "WHERE object_schema NOT IN ('{}')".format("','".join(exclueded_schemas)) + + stmt = """select object_schema, + object_name, index_name + from sys.schema_unused_indexes {} + """.format(filter_string) + + # maybe can use pd.read_sql, if that, we need to use sqlalchemy or pymysql + + try: + result = session.run_sql(stmt) + result = result.fetch_all() + if result: + result = pd.DataFrame([list(index) for index in result], columns=['schema', 'table', 'index']) + else: + result = pd.DataFrame(columns=['schema', 'table', 'index']) + except Exception as e: + print("Error: {}".format(e)) + return False + + return result + + +def _query_unused_indexes_group(addresses, user, password): + shell = mysqlsh.globals.shell + mysql = mysqlsh.mysql + pd_common_unused_indexes = pd.DataFrame() + + for address in addresses: + my_classic_session = mysql.get_session(shell.unparse_uri( + {'user': user, 'password': password, 'host': address.split(':')[0], 'port': address.split(':')[1]})) + + if pd_common_unused_indexes.empty: + pd_common_unused_indexes = _query_unused_indexes(my_classic_session) + + if pd_common_unused_indexes is False: + break + + if pd_common_unused_indexes.empty: + break + + pd_unused_indexes = _query_unused_indexes(my_classic_session) + + if pd_unused_indexes is False: + pd_common_unused_indexes = False + break + + if pd_unused_indexes.empty: + pd_common_unused_indexes = pd_unused_indexes + break + + pd_common_unused_indexes = pd.merge(pd_common_unused_indexes, pd_unused_indexes) + + return pd_common_unused_indexes + + +def _get_common_unused_indexes(session, limit, cluster_object): + shell = mysqlsh.globals.shell + + current_session_uri = session.get_uri() + addresses = [instance['address'] for instance in cluster_object.describe()['defaultReplicaSet']['topology']] + password = shell.prompt("Please Enter password again for {}: ".format(current_session_uri), + {'type': 'password'}) + + if password is None: + return + + pd.set_option('display.max_rows', limit) + + result = _query_unused_indexes_group(addresses, + shell.parse_uri(current_session_uri)['user'], + password) + + if result is False: + return + + if result.empty: + return + + print(result.head(limit)) + + pd_rows = result.shape[0] + if pd_rows > limit: + awnser = shell.prompt('Show all unused indexes? ', {'type': 'confirm', 'yes': '&yes', 'defaultValue': 'no'}) + + if awnser == '&yes': + pd.set_option('display.max_rows', pd_rows) + print(result) + return + + +@plugin_function("check.getRstUnusedIndexes") +def get_rs_unused_indexes(limit=20, session=None): + """ + Print the unused indexes in a schema or table, or all schemas and tables in a Replica Set + + Args: + limit (integer): limit the number of rows to display, default 20 + session (object): The optional session object used to query the + database. If omitted the MySQL Shell's current session will be used. + """ + dba = mysqlsh.globals.dba + shell = mysqlsh.globals.shell + + if session is None: + session = shell.get_session() + + if session is None: + print("No session specified. Either pass a session object to this " + "function or connect the shell to a database") + return + + try: + rs = dba.get_replica_set() + _get_common_unused_indexes(session, limit, rs) + + except Exception as e: + print("Error: {}".format(e)) + return + + +@plugin_function("check.getIcUnusedIndexes") +def get_ic_unused_indexes(limit=20, session=None): + """ + Print the unused indexes in a schema or table, or all schemas and tables in a cluster. + + Args: + limit (integer): limit the number of rows to display, default 20 + session (object): The optional session object used to query the + database. If omitted the MySQL Shell's current session will be used. + """ + dba = mysqlsh.globals.dba + shell = mysqlsh.globals.shell + + if session is None: + session = shell.get_session() + + if session is None: + print("No session specified. Either pass a session object to this " + "function or connect the shell to a database") + return + + try: + ic = dba.get_cluster() + _get_common_unused_indexes(session, limit, ic) + + except Exception as e: + print("Error: {}".format(e)) + return diff --git a/check/init.py b/check/init.py index 95ab5b4..65866d5 100644 --- a/check/init.py +++ b/check/init.py @@ -15,3 +15,4 @@ class check: from check import schema from check import other from check import gtid +from check import index diff --git a/requirements.txt b/requirements.txt index 60edf78..7b85d07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests~=2.31.0 -prettytable~=3.9.0 \ No newline at end of file +prettytable~=3.9.0 +pandas~=2.1.3 \ No newline at end of file