精华内容
下载资源
问答
  • 业务开发过程,其实就是用户业务数据的处理过程,因而开发的核心任务就是维护数据一致不出错。现实场景中,多个用户会并发读写同一份数据(如秒杀),不加控制...并行事务在本文中,假定所有读写操作,不管是一次操作...

    业务开发过程,其实就是用户业务数据的处理过程,因而开发的核心任务就是维护数据一致不出错。现实场景中,多个用户会并发读写同一份数据(如秒杀),不加控制会翻车、加了控制则降低并发度,影响性能和用户体验。

    如何优雅的进行并发数据控制呢?本质上需要解决两个问题:

    读-写冲突

    写-写冲突

    让我们看下,最常见的MySQL InnoDB存储引擎是如何协调上述两个问题的?

    并行事务

    在本文中,假定所有读写操作,不管是一次操作,还是一串操作,都是在事务中进行的,这就涉及事务的ACID特性(原子性、一致性、隔离性、持久性)

    ACID

    解释

    原子性

    序列操作要么全都完成、要么全都失败

    一致性

    一个事务只有提交后,其操作数据才可被其他事务看见

    隔离性

    适度破坏一致性、使得事务可以在不同程度上看到其他并行事务的数据,提升性能

    持久性

    事务提交后持久到磁盘不会丢失

    在并发冲突问题下,重点关注并行事务下隔离性与一致性

    要数据完全一致,就意味着所有并行事务都得串行执行,对于高并发的数据库应用来说显然无法接受,所以现代数据库应用为了提高性能都会适度破坏一致性,破坏程度的不同对应了数据库标准的不同隔离级别

    事务隔离级别

    Serializable 序列化

    SQL规范

    顾名思义,将并行事务的执行顺序串行化

    事务A

    事务B

    事务C

    begin

    begin

    begin

    select/insert/update/delete

    commit

    select/insert/update/delete

    commit

    select/insert/update/delete

    commit

    InnoDB实现

    读写都加表级别的排它锁

    实际效果

    并行事务串行执行,因而不存在冲突,每次操作的数据一定是最新的数据,虽然数据完全一致,但是性能差,毫无并发度可言,一般不用,不过多阐述, 只要记住select也是会隐式加排它锁的就行

    Repeatable Read 可重复读(默认)

    SQL规范

    事务多次读取同样条件下的数据依然可以读到同样的数据,不管其他事务如何操作同样条件的数据

    事务A

    事务B

    begin

    begin

    select a from table where id =1 => a =1

    update table set a = 2 where id = 1

    commit

    select a from table where id =1 => a =1

    commit

    InnoDB实现

    在该级别下,MVCC(多版本并发控制)处理读-写冲突、 2PL(两阶段锁)处理写-写冲突

    MVCC

    SELECT * FROM table WHERE id = 1

    复制代码

    上述这种普通的Select语句在Repeatable Read级别下执行的是快照读。MVCC机制给每条数据都额外增加两个字段,一个用于记录当前事务id(新事务自增),另一个用于指向undo log版本链中的上一个版本(事务每次更新记录,mysql都会写一条undo log,方便事务回滚),同时给事务开启一个ReadView,展示当前还在活动的事务id

    5a4379ccdf40fbf3f29f6fa99ee06656.png

    假设由Insert事务提交产生一条原始记录a,事务101连续更新两次a值,但未提交,这时事务102要查询a值,在事务开始时生成ReadView,记录当前活动事务[101],查找a=4的记录发现事务id为101,位于ReadView中,于是顺着版本链,找到第一条事务id不在ReadView中的a=1,返回

    这时即便事务101提交了,由于事务102的ReadView依然是事务开始那一刻的[101],因而后续读到的还是a=1

    快照读的存在,使得读取数据不需要加锁,提高了读操作的性能,但这也意味着数据有可能是历史版本的,是弱一致的,如果后续存在Update操作,并且基于本次读出来的数据,那么update写入的数据实际上是错误的(也称为第二类丢失更新,其他事务update的记录也会被这次错误的update覆盖)

    对于这类场景,我们可以通过在业务上使用乐观锁,在每条记录增加一个版本号字段,每次更新前比对,也可以手动加锁(悲观锁),将快照读升级为当前读

    SELECT * FROM table WHERE id = 1 lock in share mode//加读锁

    SELECT * FROM table WHERE id = 1 for update//加写锁

    复制代码2PL

    这种操作数据必须读取当前版本数据的,叫做当前读,包括显式加锁的SELECT 和隐式加写锁的INSERT、UPDATE、DELETE。

    在2PL的加锁阶段,只允许加锁操作,已有读锁可以加读锁但不能再加写锁,已有写锁则不能再加任何锁,无法获取锁的事务只能阻塞等待,直到持锁事务在解锁阶段(事务提交)释放锁后才能继续竞争锁并执行

    与Serializable的表级锁相比,Repeatable Read的锁粒度更细,是行级锁,只会锁住所需要的数据行,支持更大的并发度,不过若查询时所需数据太多或者没有走索引,还是会升级成表级锁(行锁实际上加在索引上,没有索引则锁全表)

    实际效果

    数据库是读多写少的高并发应用,MVCC的机制牺牲了数据的强一致性,使得大部分只读事务不用阻塞等待,而且由于行级锁的粒度更小,只有对相同条件的数据加锁时才会冲突,极大提高了并发,不过需要注意第二类丢失更新的存在

    数据库的操作除了SELECT、UPDATE、DELETE外,还有INSERT,SQL规范中,该级别的INSERT会引起幻读的可能性:由于是行级锁,事务A加行锁update某个范围的数据后,事务B在这个范围内插入一条记录,这时事务A发现多了一条数据,而且没有被自己update

    实际上MySQL的实现中,利用了GAP锁(间隙锁)在一定索引区间内加锁,锁住甚至不需要的数据,防止其他事务往其中插入数据,这样行锁+GAP锁组成的Next-Key锁就同时防止别的事务修改、删除和插入,解决了幻读的问题

    深入理解

    当事务一开始读不到的记录被另外的事务插入时,该事务在update同样条件的记录后,再次读却能读到

    在RR级别却出现了不可重读的现象

    事务A

    事务B

    begin

    begin

    select a from table where id = 1 => null

    insert into table(id,a) values(1,1)

    commit

    select a from table where id = 1 => null

    update table set a = 2 where id = 1

    select a from table where id = 1 => a = 2

    commit

    原因在于 update是当前读,可以读到最新记录,在更新后,将事务A的事务id赋值到记录上,因而这条记录对于事务A来说是第一条不位于ReadView中的数据,因而可以读到

    Read Committed 读已提交

    SQL规范

    事务可以读到其他事务已经提交的数据

    事务A

    事务B

    begin

    begin

    select a from table where id = 1 => a=1

    update table set a = 2 where id = 1

    select a from table where id = 1 => a=1

    commit

    select a from table where id = 1 => a=2

    commit

    InnoDB实现

    与RR级别类似,RC级别也是采用MVCC处理读-写冲突、 2PL处理写-写冲突

    MVCC

    该级别下,普通的Select语句,仍然是快照读,与RR最大的区别在于:RC每次select都会生成ReadView,例如事务103第一次select生成的ReadView为[101,102],当事务101提交后,事务103再次select生成的新的ReadView为[102]了,可以读到101提交后的数据

    因此在一次长事务中的多次读取,只要其他事务有更新提交,就会读到,所以是不可重读的

    同样,快照读也可能是历史数据,基于历史数据作出的变更再update就会存在丢失更新的现象,解决方案同RR级别的乐观锁与悲观锁

    2PL

    Update、Insert、Delete操作同样会加写锁,区别是该级别下只有行锁,并没有Gap锁和Next-Key锁,所以操作不当就会引发幻读

    实际效果

    与MySQL官方默认的RR级别相比,RC级别一致性程度更低,存在很多诸如不可重读、幻读、丢失更新等问题,乍一看没那么受待见,但据说这是阿里内部使用的数据库隔离级别

    可以看到,RC级别的加锁很少,减少并行事务因锁冲突阻塞等待的概率,也减少了死锁出现的可能(但还是会有),因而也支持更大的并发

    只是需要多花点心思控制下一致性而已

    Read Uncommitted 读未提交

    SQL规范

    事务可以读到其他事务未提交的数据

    事务A

    事务B

    begin

    begin

    select a from table where id = 1 => a=1

    update table set a = 2 where id = 1

    select a from table where id = 1 => a=2

    commit

    commit

    InnoDB实现

    读写都不加锁,并不存在冲突,直接读最新的数据,也就不需要MVCC处理读写冲突了

    实际效果

    读读,读写,写写都可以并行,没有一致性保证,性能也不见得提升太多,假如事务A在读到事务B更新的数据后,事务B回滚,事务A就相当于读到了一条不存在的数据,也就是所谓的脏读,这样实际情况中业务容易出问题,一般也不用,不过多阐述

    小结

    数据库是高并发应用,场景各异,不可能一套标准走天下,因而提供了四种隔离级别,供开发者权衡,但主要还是RR和RC两种

    要保证写的一致性,避免不了加锁,不管是Insert、Update、Delete自带的锁还是select …… for update/in share mode这种在业务控制层面的锁,要降低锁的影响,就需要控制好锁的覆盖范围

    关于读的一致性,如果用二八原则来看待的话,可以简单地认为80%的操作都是只读操作,在这种场景下,即使第一次看到的是历史数据,刷新几下,总能看到最新数据的,如果还需要加锁来保证强一致,反而更加影响性能,因此包括MySQL在内的主流关系型数据库,都采用无锁的典型方案MVCC来实现读的弱一致

    推荐阅读

    展开全文
  • 但是每次循环都会再次从数据库中读取大量的数据,数据的条数超过了几万条,这还不算,在每次读取后,计算出分数,会将分数直接写入数据库,就这样,在频繁的与数据库进行读写操作的时候,mysql不干了,直接将这个表...

    问题介绍

    最近在给学校做的一个项目中,有一个功能涉及到考核分数问题。

    我当时一想,这个问题并不是很难,于是就直接采用了这样的方法:拿着一个表中的数据作为索引,去挨个遍历相关表中的数据,最后经过算分的过程,直接在算分函数中将算出的分数直接写入数据库,这就导致了标题说的问题。

    解决过程

    这个问题出现的现象是这样的:我从数据库中读取出来的作为索引的数据共有2000多条,使用增强for循环将数据传入算分的方法中。但是后来发现,算出分数的数据总计也就300多条,本以为是这条作为索引的数据在其他表中没有相关的数据造成的分数为0,后来发现,即使是在其他表中有数据的,算出的分数依旧是0。

    这个问题着实令人费解,本以为是增强for循环的线程安全问题,后来就将增强for循环改成了迭代器,但是这个问题依旧没有解决。

    紧接着,我想了想,开始测试一下这条分数为0的数据是否被读入了函数当中,于是我在算分的函数中设置了个判断条件,结果发现,这条数据真的是没有被读入函数。

    无奈之下,只能是一点点debug,我将传入的参数改成了数据表中没有分数的这条数据的,居然发现这条数据能够被成功计算出分数。

    这时候我整个人已经是懵逼的了,我不禁在想,为什么会出现这种情况?单独将值传入函数,可以计算出分数值,但所有数据一起读写,就会出现这个问题呢?

    此时我已经坐在电脑前debug了一个下午,晚饭也没有吃,本来中午就应该上线的功能,硬是到了晚上依然没有被解决,确实有点慌了。

    无奈之下只能是请教之前负责这个项目的学长,但是学长有课,只能等到八点半。在等待期间,我依旧是在疯狂的debug,真的是一点头绪都没有。

    学长来了之后,他在循环的过程中加入了一个计算次数的变量,每次输出一下,结果发现,程序并没有执行完成,总是执行到第420次就停了,期间没有任何错误信息,没有任何其他异常,诡异的很。

    不过这个时候我们已经意识到了是mysql数据库的问题。

    就这样,学长和我们一起一直盯到了两点,之前已经将分数采用一次读取几百条数据的方式成功将分数算出来交给学校科研院处理了。

    随着学长一个激动的叫声,问题总算是得到了解决!

    最终的解决方案

    原来,在我设计的方法中,是读写同时进行的。

    虽然第一次将数据读到一个集合中了,但是每次循环都会再次从数据库中读取大量的数据,数据的条数超过了几万条,这还不算,在每次读取后,计算出分数,会将分数直接写入数据库,就这样,在频繁的与数据库进行读写操作的时候,mysql不干了,直接将这个表锁住了。

    这也解释了为什么系统的其他的功能可以使用,偏偏和分数相关的功能都不能用的问题。

    于是最终将写入分数的步骤单独拿了出来,这样子,成功的将所有数据的分数都算了出来。

    持续了超过十个小时的过程中,终于将问题解决了。

    经验教训

    经过这次惨痛的教训,使我深刻明白了一个问题,下次再设计方法的时候,一定要将读写操作分开进行,否则就会出现十分严重的问题。

    虽然整个过程困难无比,但还是收获巨大的,这种感觉真的特别好。

    我想以后真的不可能会忘记读写分离这件事情了,哈哈!

    谢谢帮忙的学长还有同学!

    结语

    想了解更多的专业知识、前瞻信息、技术文章吗?那么请我的个人公众号:进击的程序狗,一起进步!

    展开全文
  • 这篇主要记录一下如何实现对数据库的并行运算来节省代码运行时间。...显然,几十万条数据要是一条条读写,然后在本机上操作,耗时太久,可行性极低。所以,如何有效并行的读取内容,并且进行操作...

    这篇主要记录一下如何实现对数据库的并行运算来节省代码运行时间。语言是Python,其他语言思路一样。

    前言

    一共23w条数据,是之前通过自然语言分析处理过的数据,附一张截图:

    3c1682b97879330f5f5e134458dbc98a.png

    要实现对news主体的读取,并且找到其中含有的股票名称,只要发现,就将这支股票和对应的日期、score写入数据库。

    显然,几十万条数据要是一条条读写,然后在本机上操作,耗时太久,可行性极低。所以,如何有效并行的读取内容,并且进行操作,最后再写入数据库呢?

    并行读取和写入

    并行读取:创建N*max_process个进程,对数据库进行读取。读取的时候应该注意:

    每个进程需要分配不同的connection和对应的cursor,否则数据库会报错。

    数据库必须能承受相应的高并发访问(可以手动更改)

    实现的时候,如果不在进程里面创建新的connection,就会发生冲突,每个进程拿到权限后,会被下个进程释放,所以汇报出来NoneType Error的错误。

    并行写入:在对数据库进行更改的时候,不可以多进程更改。所以,我们需要根据已有的表,创建max_process-1个同样结构的表用来写入。表的命名规则可以直接在原来基础上加上1,2,3...数字可以通过对max_process取余得到。

    此时,对应进程里面先后出现读入的conn(保存消息后关闭)和写入的conn。每个进程对应的表的index就是 主循环中的num对max_process取余(100->4,101->5),这样每个进程只对一个表进行操作了。

    部分代码实现

    max_process = 16 #最大进程数

    def read_SQL_write(r_host,r_port,r_user,r_passwd,r_db,r_charset,w_host,w_port,w_user,w_passwd,w_db,w_charset,cmd,index=None):

    #得到tem字典保存着信息

    try:

    conn = pymysql.Connect(host=r_host, port=r_port, user=r_user, passwd =r_passwd, db =r_db, charset =r_charset)

    cursor = conn.cursor()

    cursor.execute(cmd)

    except Exception as e:

    error = "[-][-]%d fail to connect SQL for reading" % index

    log_error('error.log',error)

    return

    else:

    tem = cursor.fetchone()

    print('[+][+]%d succeed to connect SQL for reading' % index)

    finally:

    cursor.close()

    conn.close()

    try:

    conn = pymysql.Connect(host=w_host, port=w_port, user=w_user, passwd =w_passwd, db =w_db, charset =w_charset)

    cursor = conn.cursor()

    cursor.execute(cmd)

    except Exception as e:

    error = "[-][-]%d fail to connect SQL for writing" % index

    log_error('error.log',error)

    return

    else:

    print('[+][+]%d succeed to connect SQL for writing' % index)

    r_dict = dict()

    r_dict['id'] = tem[0]

    r_dict['content_id'] = tem[1]

    r_dict['pub_date'] = tem[2]

    r_dict['title'] = cht_to_chs(tem[3])

    r_dict['title_score'] =tem[4]![](http://images2015.cnblogs.com/blog/1172464/201706/1172464-20170609000900309-1810357590.png)

    r_dict['news_content'] = cht_to_chs(tem[5])

    r_dict['content_score'] = tem[6]

    for key in stock_dict.keys():

    #能找到对应的股票

    if stock_dict[key][1] and ( r_dict['title'].find(stock_dict[key][1])!=-1 or r_dict['news_content'].find(stock_dict[key][1])!=-1 ):

    w_dict=dict()

    w_dict['code'] = key

    w_dict['english_name'] = stock_dict[key][0]

    w_dict['cn_name'] = stock_dict[key][1]

    #得到分数

    if r_dict['title_score']:

    w_dict['score']=r_dict['title_score']

    else:

    w_dict['score']=r_dict['content_score']

    #开始写入

    try:

    global max_process

    cmd = "INSERT INTO dyx_stock_score%d VALUES ('%s', '%s' , %d , '%s' , '%s' , %.2f );" % \

    (index%max_process ,r_dict['content_id'] ,r_dict['pub_date'] ,w_dict['code'] ,w_dict['english_name'] ,w_dict['cn_name'] ,w_dict['score'])

    cursor.execute(cmd)

    conn.commit()

    except Exception as e:

    error = " [-]%d fail to write to SQL" % index

    cursor.rollback()

    log_error('error.log',error)

    else:

    print(" [+]%d succeed to write to SQL" % index)

    cursor.close()

    conn.close()

    def main():

    num = 238143#数据库查询拿到的总数

    p = None

    for index in range(1,num+1):

    if index%max_process==1:

    if p:

    p.close()

    p.join()

    p = multiprocessing.Pool(max_process)

    r_cmd = ('select id,content_id,pub_date,title,title_score,news_content,content_score from dyx_emotion_analysis where id = %d;' % (index))

    p.apply_async(func = read_SQL_write,args=(r_host,r_port,r_user,r_passwd,r_db,r_charset,w_host,w_port,w_user,w_passwd,w_db,w_charset,r_cmd,index,))

    if p:

    p.close()

    p.join()

    展开全文
  • openmp只在作业里用过,不太了解,挺傻瓜的,上手简单,给个命令他会自动把你的计算并行化,读写就不是很了解了。mpi的话写过fortran和python。关键点在于告知数据位置(hyperslab)。比如说我要把我100*100的矩阵分成...

    openmp只在作业里用过,不太了解,挺傻瓜的,上手简单,

    给个命令他会自动把你的计算并行化,读写就不是很了解了。

    mpi的话写过fortran和python。

    关键点在于告知数据位置(hyperslab)。比如说我要把我100*100的矩阵分成10*10的block,我需要告诉代码我的分配规则。比如说,rank0 写在第一行第一列,rank1写在第一行第二列,,,,我就可以写col=rank%10,row=rank/10,(从0计数),写的时候每个processor会告知,mydata,myrank,myposition,然后并行的时候每个核的数据就会被写在相应的位置。

    给个fortran的例子,

    CALL h5sselect_hyperslab_f (filespace, H5S_SELECT_SET_F, offset, count, error, stride,block_size)

    这里filespace 告知在文件里开辟的内存id,第二个内置参数,告诉怎样写,后面offset,count,stride,block什么的全都用于描述位置,你可以横着写,数着写,跳着写,转着圈写。。。一般我只用两个参数就能描述清楚了,因为我搞的是结构网格,一开始还不理解为什么那么多种写数据的花型。直到后面写作业用到非结构网格,这个时候描述数据位置就尤其重要了,因为你需要让相邻processor处理的数据物理空间也尽量靠近,这样才能减少block之间的信息交互,然后提高计算速度。

    代码不粘了,不知道国内用的多不。当时官网教程都不给全,自己瞎胡整整好了,痛苦.png。需要者私。

    展开全文
  • 背景基于 COMMIT_ORDER 的并行复制只有在有压力的情况下才可能会形成一组,压力不大的情况下在从库的并行度并不会高。但是基于 WRITESET 的并行复制目标就是在ORDER_COMMIT 的基础上再尽可能的降低 last commit ,...
  • 数据库的主从同步读写分离一般都是在多台服务器上部署,但是咱没那么多服务器可用,但是应用的瓶颈主要在数据库上,那么我们就可以在单台服务器上配置多个从数据库,并行读取数据提高应用响应速度。复制出第二个 ...
  • mysql-proxy实现mysql读写分离

    千次阅读 2018-08-16 00:26:48
    基于主从复制进行 mysql读写分离 主重复制的相关信息可参看的我博客mysql主从复制(基于GTID的主从复制、并行复制、半同步复制) 注:以下实验是基于mysql 基于GTID 的半同步复制模式进行的在此条件下进行的 ...
  • mysql并行复制降低主从同步延时的思路与启示 mysql并行复制降低主从同步延时的思路与启示 mysql并行复制降低主从同步延时的思路与启示 原创2016-06-0858沈剑架构师之路 一、缘起 mysql主从复制,...
  • mysql主从复制,读写分离是互联网用的非常多的mysql架构,主从复制最令人诟病的地方就是,在数据量较大并发量较大的场景下,主从延时会比较严重。   为什么mysql主从延时这么大? 回答:从库使用【单线程】重放...
  • mysql主从复制,读写分离是互联网用的非常多的mysql架构,主从复制最令人诟病的地方就是,在数据量较大并发量较大的场景下,主从延时会比较严重。 为什么mysql主从延时这么大? 回答:从库使用【单线程】重放...
  • 1.主数据库(Master)将变更信息写入到二进制日志文件中,这里需要注意的是旧版本的MySQL数据库默认是不开启二进制日志的,强烈建议在安装好数据库启动之前一定要先检查一下二进制日志文件是否开启,即使不做主从...
  • [python]gevent+PyMysql实现mysql并行查询

    千次阅读 2018-04-09 14:01:13
    PyMysql是pure python的mysql驱动,因此可以通过gevent实现协程化,从而实现基于协程的mysql查询.比基于多线程的连接池效率更高.(单纯查询场景,不考虑读写混合的事务问题)测试代码参考峰云的博客.在win7, python2/3...
  • server10和server11安装mysql: yum install -y mysql-community-client-5.7.17-1.el6.x86_64.rpm mysql-community-common-5.7.17-1.el6.x86_64.rpm mysql-community-libs-5.7.17-1.el6.x86_64.rpm mysql-...
  • 但是每次循环都会再次从数据库中读取大量的数据,数据的条数超过了几万条,这还不算,在每次读取后,计算出分数,会将分数直接写入数据库,就这样,在频繁的与数据库进行读写操作的时候,mysql不干了,直接将这个表...
  • 但是每次循环都会再次从数据库中读取大量的数据,数据的条数超过了几万条,这还不算,在每次读取后,计算出分数,会将分数直接写入数据库,就这样,在频繁的与数据库进行读写操作的时候,mysql不干了,直接将这个表...
  • 业务开发过程,其实就是用户业务数据的处理过程,因而开发的核心任务就是维护数据一致不出错。现实场景中,多个用户会并发读写同一份数据(如秒杀),不加控制...并行事务在本文中,假定所有读写操作,不管是一次操作...
  • 那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请...
  • 其中UUID是一个MySQL实例的唯一标识。TID代表了该实例上已经提交的事务数量,并且随着事务提交单调递增,所以GTID能够保证每个MySQL实例事务的执行(不会重复执行同一个事务,并且会补全没有执行的事务)。下面是一...
  • 优化与并行MySQL会解析查询,并创建解析树,然后对其进行各种优化,包括重写查询,决定表的读取顺序,以及选择合适的索引等。并发控制:1.读写锁:读锁为共享锁,相互不阻塞。写锁为排他锁,持有写锁会阻塞其他...
  • 基于CentOS7,MySQL5.7的主从复制+读写分离+读写分离的高可用架构搭建实战2 一、半同步复制 二、并行复制 三、读写分离 四、双主模式 五、MHA架构 六、分库分表
  • 优化与并行MySQL会解析查询,并创建解析树,然后对其进行各种优化,包括重写查询,决定表的读取顺序,以及选择合适的索引等。并发控制:1.读写锁:读锁为共享锁,相互不阻塞。写锁为排他锁,持有写锁会阻塞其他...
  • MySQL读写分离的原理 1、主服务器配置Binlog,将改变记录到二进制文件(binary log). 2、从服务器配置RelayLog.Slave将master的binary log events拷贝到它的中继日志(relay log)。 3、在主服务器授权Slave账户...

空空如也

空空如也

1 2 3 4 5 ... 15
收藏数 285
精华内容 114
关键字:

mysql并行读写

mysql 订阅