pymysql访问tidb分区表无返回数据(mysql没问题)

【 TiDB 使用环境】
集群版本:5.7.25-TiDB-v5.3.1
PyMySQL==0.9.3

【概述】 场景 + 问题概述
1.pymysql访问tidb分区表首次执行select * from t limit 1 无返回数据,访问相同表结构的非分区表select * from t2 limit 1 有返回数据;
2.在MySQL 5.7.26执行都没问题,都可以正常读取数据

【应用框架及开发适配业务逻辑】
查询分区表数据,SQL为最简单的方式:select * from t limit 1

【背景】 做过哪些操作
【现象】 业务和数据库现象
【问题】 当前遇到的问题

  1. 两个表结构,t1为分区表,t2为相同表结构的非分区表
    CREATE TABLE t (
    id varchar(255) NOT NULL ,
    logdt timestamp DEFAULT ‘1970-01-02 00:00:00’,
    name varchar(255) NOT NULL ,
    newdate datetime DEFAULT CURRENT_TIMESTAMP ,
    updatetime datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
    UNIQUE KEY id (id,logdt),
    KEY newdate_gid_dt (newdate,logdt)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T! SHARD_ROW_ID_BITS=4 PRE_SPLIT_REGIONS=3 */
    PARTITION BY RANGE ( UNIX_TIMESTAMP(logdt) ) (
    PARTITION p20220520 VALUES LESS THAN (1653062400),
    PARTITION p20220521 VALUES LESS THAN (1653148800),
    PARTITION p20220522 VALUES LESS THAN (1653235200),
    PARTITION p20220523 VALUES LESS THAN (1653321600)
    );

CREATE TABLE t2 (
id varchar(255) NOT NULL ,
logdt timestamp DEFAULT ‘1970-01-02 00:00:00’,
name varchar(255) NOT NULL ,
newdate datetime DEFAULT CURRENT_TIMESTAMP ,
updatetime datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
UNIQUE KEY id (id,logdt),
KEY newdate_gid_dt (newdate,logdt)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T! SHARD_ROW_ID_BITS=4 PRE_SPLIT_REGIONS=3 */;

2.两个表都有数据,如下
mysql> select *from t;
±----±--------------------±-----±--------------------±--------------------+
| id | logdt | name | newdate | updatetime |
±----±--------------------±-----±--------------------±--------------------+
| id1 | 2022-05-20 18:00:00 | tidb | 2022-05-24 18:00:44 | 2022-05-24 18:00:44 |
| id1 | 2022-05-21 18:00:00 | tidb | 2022-05-24 18:00:49 | 2022-05-24 18:00:49 |
±----±--------------------±-----±--------------------±--------------------+
2 rows in set (0.01 sec)

mysql> select *from t2;
±----±--------------------±-----±--------------------±--------------------+
| id | logdt | name | newdate | updatetime |
±----±--------------------±-----±--------------------±--------------------+
| id1 | 2022-05-21 18:00:00 | tidb | 2022-05-24 18:02:38 | 2022-05-24 18:02:38 |
±----±--------------------±-----±--------------------±--------------------+
1 row in set (0.00 sec)

3.pymysql第一次查询分区表,无法返回数据;第二次查询有数据,第三次查也有数据;
1)执行操作1:查分区表无数据返回,查非分区表有数据
import pymysql

conn = pymysql.connect(**{
‘db’: ‘test’,
‘database’: ‘test’,
‘user’: ‘xxx’,
‘password’: ‘xxx’,
‘host’: ‘xxx’,
‘port’: 4000,
‘cursorclass’: pymysql.cursors.DictCursor
})

#分区表无返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

#非分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t2 limit 1”)
print(cur.fetchall())

执行python a.py输出结果:
()
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’logdt’: datetime.datetime(2022, 5, 21, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]

2)执行操作2:调整查询表的顺序,这一次查询分区表和非分区表都有数据返回
import pymysql

conn = pymysql.connect(**{
‘db’: ‘test’,
‘database’: ‘test’,
‘user’: ‘xxx’,
‘password’: ‘xxx’,
‘host’: ‘xxx’,
‘port’: 4000,
‘cursorclass’: pymysql.cursors.DictCursor
})

#非分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t2 limit 1”)
print(cur.fetchall())

#分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

执行python a.py输出结果:
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’logdt’: datetime.datetime(2022, 5, 21, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 0, 44), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 0, 44), u’logdt’: datetime.datetime(2022, 5, 20, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]

3)执行操作3:pymysql第一次查询分区表,无法返回数据;第二次查询有数据
import pymysql

conn = pymysql.connect(**{
‘db’: ‘test’,
‘database’: ‘test’,
‘user’: ‘xxx’,
‘password’: ‘xxx’,
‘host’: ‘xxx’,
‘port’: 4000,
‘cursorclass’: pymysql.cursors.DictCursor
})

#分区表无返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

#分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

#非分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t2 limit 1”)
print(cur.fetchall())

执行python a.py输出结果:
()
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 0, 49), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 0, 49), u’logdt’: datetime.datetime(2022, 5, 21, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’logdt’: datetime.datetime(2022, 5, 21, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]

4)执行操作4:pymysql第一次查询分区表,无法返回数据;第二次查询有数据;第三次查询有数据
import pymysql

conn = pymysql.connect(**{
‘db’: ‘test’,
‘database’: ‘test’,
‘user’: ‘xxx’,
‘password’: ‘xxx’,
‘host’: ‘xxx’,
‘port’: 4000,
‘cursorclass’: pymysql.cursors.DictCursor
})

#分区表无返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

#分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

#分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t limit 1”)
print(cur.fetchall())

#非分区表有返回数据
cur = conn.cursor()
cur.execute(“select * from t2 limit 1”)
print(cur.fetchall())

执行python a.py输出结果:
()
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 0, 49), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 0, 49), u’logdt’: datetime.datetime(2022, 5, 21, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 0, 44), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 0, 44), u’logdt’: datetime.datetime(2022, 5, 20, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]
[{u’newdate’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’updatetime’: datetime.datetime(2022, 5, 24, 18, 2, 38), u’logdt’: datetime.datetime(2022, 5, 21, 18, 0), u’id’: ‘id1’, u’name’: ‘tidb’}]

【业务影响】
无法读取分区表数据

【TiDB 版本】
集群版本:5.7.25-TiDB-v5.3.1

1赞

是否可以考虑尝试升级一下依赖?在 PyMySQL==1.0.2 下就无法复现这个步骤了:

代码如下:

import pymysql

conn = pymysql.connect(**{
    'database': 'test',
    'user': 'root',
    'password': '',
    'host': '127.0.0.1',
    'port': 4000,
    'cursorclass': pymysql.cursors.DictCursor
})

cur = conn.cursor()
cur.execute("select * from t limit 1")
print(f"t result: {cur.fetchall()}")

cur = conn.cursor()
cur.execute("select * from t2 limit 1")
print(f"t2 result: {cur.fetchall()}")

输出如下:

t result: [{'id': 'id1', 'logdt': datetime.datetime(2022, 5, 21, 18, 0), 'NAME': 'tidb', 'newdate': datetime.datetime(2022, 5, 24, 18, 0, 49), 'updatetime': datetime.datetime(2022, 5, 24, 18, 0, 49)}]
t2 result: [{'id': 'id1', 'logdt': datetime.datetime(2022, 5, 21, 18, 0), 'name': 'tidb', 'newdate': datetime.datetime(2022, 5, 24, 18, 2, 38), 'updatetime': datetime.datetime(2022, 5, 24, 18, 2, 38)}]

PyMySQL 1.0.2 还是有类似的问题

我试试换个集群

目前定位到可能和集群的某些配置有关

问题解决了吗?

有问题的配置:
mysql> select @@tidb_partition_prune_mode;
±----------------------------+
| @@tidb_partition_prune_mode |
±----------------------------+
| dynamic |
±----------------------------+
1 row in set (0.00 sec)

正常:mysql> select @@tidb_partition_prune_mode;
±----------------------------+
| @@tidb_partition_prune_mode |
±----------------------------+
| static |
±----------------------------+
1 row in set (0.00 sec)

调整tidb_partition_prune_mode为static后pymysql查询正常

是的,现在已经解决了,感谢

好叻~下次有问题再随时上来问哟~

1赞

1.分区表动态裁剪功能:dynamic 模式让执行计划更简单清晰,省略 Union 操作可提高执行效率。还解决了两个 static 模式无法解决的问题:不能使用 Plan Cache,不能使用 IndexJoin 的执行方式

2.重现必须参数:
set @@tidb_partition_prune_mode=“dynamic”;
set autocommit = 0;
连接的session 里同时设置这俩就是必现,如果开启自动提交是没问题的

3.我们的集群autocommit都是为1,即都开启了自动提交;

4.出现的问题是因为pymysql 会默认关闭自动提交,刚好触发上述问题,在配置dynamic的前提下 理论上任何擅自修改、关闭自动提交功能的数据库驱动,都会触发上面的现象

pymysql执行cur.execute("select @@autocommit; ")输出 [{’@@autocommit; ': 0}]

5.解决方法:
1)关闭动态裁剪功能
2)打开动态裁剪功能,在使用驱动的时候显式指定开启自动提交功能;
但是如果业务要求关闭自动提交,则在session里将dynamic调为static,临时关闭动态裁剪功能