如何保证多线程从mysql数据库查询的数据不重复

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何保证多线程从mysql数据库查询的数据不重复相关的知识,希望对你有一定的参考价值。

mysql来说,可能出现脏读、不可重复读以及幻读,mysql默认设置是可重复读,即一次事务中不会读取到不同的数据。
可以做如下操作:
1)打开两个客户端,均设置为RR;
2)在一个事务中,查询某个操作查到某份数据;比如是某个字段version=1存在数据;
3)在另一个事务中,删除这份version=1的数据;删除后,在2所属的事务中查询数据是没有变化的,还是存在version=1的数据;
4)当我们在2所属的事务中继续更新数据,那么会发现更新不了,明明我们就看到了这份version=1的数据;
缓存一致性:
缓存一致,与什么一致?是与数据库一致,对外查询每个时刻一致;所以在针对于缓存与数据库之间该先更新哪一个呢?可能有人觉得我先更新数据库,再更新缓存不就行了吗?但是有想过个问题吗?
当用户已经支付成功了,更新到数据库,但是呢?你还在缓存中显示未支付,在用户点击频率很高并且数据库压力过大,来不及同步到缓存时,那你是不是很尴尬,这就是典型的不一致了。此时用户再支付,那你又告诉他已经支付了,那他会把你骂死的
那该怎么来做呢?我们可以这样,先更新缓存再更新数据库,那么存在什么问题呢?
1)缓存更新成功,但是数据库更新失败,而被其它的并发线程访问到
2)缓存淘汰成功,但是数据库更新失败,这也会引发后期数据不一致
参考技术A

在MySQL 8.0 之前, 我们假设一下有一条烂SQL,

mysqlselect * from t1 order by rand() ;

以多个线程在跑,导致CPU被跑满了,其他的请求只能被阻塞进不来。那这种情况怎么办? 


大概有以下几种解决办法:

    设置max_execution_time 来阻止太长的读SQL。那可能存在的问题是会把所有长SQL都给KILL 掉。有些必须要执行很长时间的也会被误杀。

    自己写个脚本检测这类语句,比如order by rand(), 超过一定时间用Kill query thread_id 给杀掉。

    那能不能不要杀掉而让他正常运行,但是又不影响其他的请求呢?

    那mysql 8.0 引入的资源组(resource group,后面简写微RG)可以基本上解决这类问题。

    比如我可以用 RG 来在SQL层面给他限制在特定的一个CPU核上,这样我就不管他,让他继续运行,如果有新的此类语句,让他排队好了。

    为什么说基本呢?目前只能绑定CPU资源,其他的暂时不行。

    那我来演示下如何使用RG。

    创建一个资源组user_ytt. 这里解释下各个参数的含义,

    type = user 表示这是一个用户态线程,也就是前台的请求线程。如果type=system,表示后台线程,用来限制mysql自己的线程,比如Innodb purge thread,innodb read thread等等。

    vcpu 代表cpu的逻辑核数,这里0-1代表前两个核被绑定到这个RG。可以用lscpu,top等列出自己的CPU相关信息。

    thread_priority 设置优先级。user 级优先级设置大于0。

    mysqlmysql> create resource group user_ytt type = user  vcpu = 0-1 thread_priority=19 enable;Query OK, 0 rows affected (0.03 sec)


    RG相关信息可以从 information_schema.resource_groups 系统表里检索。

    mysqlmysql> select * from information_schema.resource_groups;+---------------------+---------------------+------------------------+----------+-----------------+| RESOURCE_GROUP_NAME | RESOURCE_GROUP_TYPE | RESOURCE_GROUP_ENABLED | VCPU_IDS | THREAD_PRIORITY |+---------------------+---------------------+------------------------+----------+-----------------+| USR_default         | USER                |                      1 | 0-3      |               0 || SYS_default         | SYSTEM              |                      1 | 0-3      |               0 || user_ytt            | USER                |                      1 | 0-1      |              19 |+---------------------+---------------------+------------------------+----------+-----------------+3 rows in set (0.00 sec)


    我们来给语句select guid from t1 group by left(guid,8) order by rand() 赋予RG user_ytt。

    mysql> show processlist;+-----+-----------------+-----------+------+---------+-------+------------------------+-----------------------------------------------------------+| Id  | User            | Host      | db   | Command | Time  | State                  | Info                                                      |+-----+-----------------+-----------+------+---------+-------+------------------------+-----------------------------------------------------------+|   4 | event_scheduler | localhost | NULL | Daemon  | 10179 | Waiting on empty queue | NULL                                                      || 240 | root            | localhost | ytt  | Query   |   101 | Creating sort index    | select guid from t1 group by left(guid,8) order by rand() || 245 | root            | localhost | ytt  | Query   |     0 | starting               | show processlist                                          |+-----+-----------------+-----------+------+---------+-------+------------------------+-----------------------------------------------------------+3 rows in set (0.00 sec)


    找到连接240对应的thread_id。

    mysqlmysql> select thread_id from performance_schema.threads where processlist_id = 240;+-----------+| thread_id |+-----------+|       278 |+-----------+1 row in set (0.00 sec)


    给这个线程278赋予RG user_ytt。没报错就算成功了。

    mysqlmysql> set resource group user_ytt for 278;Query OK, 0 rows affected (0.00 sec)


    当然这个是在运维层面来做的,我们也可以在开发层面结合 MYSQL HINT 来单独给这个语句赋予RG。比如:

    mysqlmysql> select /*+ resource_group(user_ytt) */guid from t1 group by left(guid,8) order by rand()....8388602 rows in set (4 min 46.09 sec)


    RG的限制:

    Linux 平台上需要开启 CAPSYSNICE 特性。比如我机器上用systemd 给mysql 服务加上

    systemctl edit mysql@80 [Service]AmbientCapabilities=CAP_SYS_NICE

    mysql 线程池开启后RG失效。

    freebsd,solaris 平台thread_priority 失效。

    目前只能绑定CPU,不能绑定其他资源。

如何同时从多个线程访问 MySQL

【中文标题】如何同时从多个线程访问 MySQL【英文标题】:How to access MySQL from multiple threads concurrently 【发布时间】:2010-11-30 02:57:57 【问题描述】:

我们正在做一个 MySQL 的小型基准测试,我们想看看它对我们的数据的执行情况。

该测试的一部分是查看当多个并发线程通过各种查询冲击服务器时它是如何工作的。

MySQL documentation (5.0) 并不清楚多线程客户端。我应该指出,我确实链接到线程安全库 (libmysqlclient_r.so)

我正在使用准备好的语句并同时执行读取 (SELECT) 和写入 (UPDATE、INSERT、DELETE)。

我应该为每个线程打开一个连接吗?如果是这样:我该怎么做.. 似乎mysql_real_connect() 返回了我调用mysql_init() 时得到的原始数据库句柄) 如果不是:如何确保结果和方法(例如 mysql_affected_rows)返回正确的值,而不是与其他线程的调用发生冲突(互斥锁/锁可以工作,但感觉不对)

【问题讨论】:

【参考方案1】:

作为一个从多个线程调用 MySQL 的相当大的 C 应用程序的维护者,我可以说我在每个线程中简单地建立一个新连接没有任何问题。我遇到的一些警告:

编辑:似乎此项目符号仅适用于 this page for your appropriate version:就像你说你已经在做的那样,链接到libmysqlclient_r。 致电mysql_library_init()(一次,来自main())。阅读有关在多线程环境中使用的文档,了解其必要性。 在每个线程中使用mysql_init() 创建一个新的MYSQL 结构。这具有为您调用mysql_thread_init() 的副作用。 mysql_real_connect() 像往常一样在每个线程中,具有线程特定的 MYSQL 结构。 如果您要创建/销毁大量线程,则需要在每个线程的末尾使用mysql_thread_end()(并在main() 的末尾使用mysql_library_end())。无论如何,这是一种很好的做法。

基本上,不要共享 MYSQL 结构或为该结构创建的任何特定内容(即 MYSQL_STMTs),它会按您的预期工作。

这似乎比为我建立一个连接池要少。

【讨论】:

这正是我需要的答案。我没有意识到我必须在每个线程中调用 mysql_init ——我只是在 main() 中调用过一次。谢谢 @chazomaticus,您通常会使用多少线程以及打开多少连接?这是否适用于大量线程/连接?如果您有很多线程(100 到 1000),但不希望打开 1000 个连接的开销(您可能无权访问,因为 max_connections 的默认值通常设置为 100),则连接池非常有用。如果您的线程数量较少,那么您的方法将起作用。为我展示代码示例 +1。 由于 Isak 试图对 DB 施加压力,因此尽可能多的线程。我已经运行了 ~1000 次没有问题(如果所有线程都发出查询,那么它们的大部分时间都在poll() 空闲,所以它不像你想象的那样占用大量 CPU,尽管它可以吃掉一大块内存)。你说得对,max_connections 默认限制在 100,所以为了最大压力,根据需要提高。 我在 mysql 5.6.11 上看不到 libmysqlclient_r。在文档中也找不到任何多线程信息 看起来从 5.5 开始,他们停止在库的线程安全/不安全版本之间进行拆分。 (很好摆脱。)参见例如dev.mysql.com/doc/refman/5.6/en/c-api-threaded-clients.html 获取有关 5.6 上的多线程的文档。【参考方案2】:

从 mySQL 文档中我可以清楚地看到,任何特定的 MYSQL 结构都可以毫无困难地在线程中使用 - 同时在不同线程中使用 same MYSQL 结构显然会给您带来极其不可预测的结果因为状态存储在 MYSQL 连接中。

因此,要么为每个线程创建一个连接,要么使用上面建议的连接池,并使用某种互斥锁保护对该池的访问(即保留或释放连接)。

【讨论】:

这就是我所怀疑的......只需要弄清楚如何建立多个连接......现在无法让它工作 从来没有遇到过这个问题,但我通常不使用这些库。在我看来,您应该能够分配单独的 MYSQL 结构并初始化并连接每个结构。【参考方案3】:

您可以创建一个连接池。每个需要连接的线程都可以从池中请求一个空闲的。如果没有可用的连接,则您要么阻止,要么通过向其添加新连接来扩大池。

有一篇文章here 描述了连接池的优缺点(虽然它是基于 java 的)

编辑:这是一个关于connection pools in C的问题/答案

Edit2:这是一个用 C++ 编写的示例Connection Pool for MySQL 的链接。 (当你实现自己的时候,你可能应该忽略 goto 语句。)

【讨论】:

好答案..谢谢。但是我仍然无法打开多个连接,并且您链接到的示例缺少那段代码。我给了你一个 +1,但我不想接受它(还),因为它不能解决我的问题打开多个连接的问题【参考方案4】:

MySQL Threaded Clients in C

它指出 mysql_real_connect() 默认情况下不是线程安全的。需要为线程访问编译客户端库。

【讨论】:

我正在使用 libmysqlclient_r 库,所以我假设它已经编译了所有线程安全的东西。为了确定,我会在产生线程之前尝试做所有连接的东西,看看是否有帮助

以上是关于如何保证多线程从mysql数据库查询的数据不重复的主要内容,如果未能解决你的问题,请参考以下文章

mysql 联表查询主表一条数据从表多条数据查询显示重复从表条数数据问题

mysql分库分表,而且要保证每条数据唯一

如何同时从多个线程访问 MySQL

多线程批量拆分 List 导入数据库!

多线程如何并发访问SQLite数据库

如何创建线程?如何保证线程安全?