UsingMySQLasaNoSQL_MySQL
NoSQL
FROM: Using MySQL as a NoSQL - A story for exceeding 750,000 qps on a commodity server由于 MySQL 的局限性,很多站点都采用了 MySQL+Memcached 的架构。另外一些站点放弃 MySQL 而采用 NoSQL,比如 TokyoCabinet/Tyrant 等。不可否认,在做一些简单查询 (尤其 PK 查询) 的时候,NoSQL 比 MySQL 要快很多很多。而且网站上的绝大多数查询都是这样的简单查询。
像其他大规模的公司一样,DeNA 也面临过类似的问题。但最后我们采用的是一完全不同的方法, 仅使用了 MySQL。我们仍然使用 Memcached 做前端缓存(例如,预处理 HTML, 数量/摘要 信息),但在后端,既没有使用 Memcached 缓存任何记录,也没有使用 NoSQL,这是为什么呢?因为与其他的 NoSQL 产品相比,我们的 MySQL 能得到更好的性能。 在我们的基准测试中,一普通的 MySQL/InnoDB 5.1 服务器达到了 750,000+ QPS,生产环境中的性能当然更不列外。或许,你们很难相信这个数字,但这是事实。我将在以下分享我们的经验。
(作者经历)2010-08,我离开了 Oracle, 现在任职于日本最大社交游戏平台供应商之一的 DeNA。
在每秒中,需要做多少次的 PK 查询了?在 DeNA 公司的应用中,经常要进行 PK 查询。比如根据 user id 取出 userinfo,根据 diary id 取出日志内容, 对于这样的需求,不用说,Memcached 和 NoSQL 都相当适合。在简单的多线程 “Memcached GET”基准测试中,很可能每秒进行 400,000 次 get 操作,即使 Memcached client 在不同的服务器。在一台 Nehalem box 2.5GHz x 8 核 CPU, Broadcom 四端口千兆网卡的服务器上,最新的 libMemcached 和 Memcached 每秒可达到 420,000 次 get 操作。
在 MySQL 下, 每秒可作多少次的 PK 查询呢, 我们可用 sysbench, super-smack or mysqlsla 等来进行基准测试
[matsunobu@host ~]$ mysqlslap --query="select user_name,.. from test.user where user_id=1" \\ --number-of-queries=10000000 --concurrency=30 --host=xxx -uroot
[matsunobu@host ~]$ mysqladmin extended-status -i 1 -r -uroot \\ | grep -e "Com_select"
... | Com_select | 107069 | | Com_select | 108873 | | Com_select | 108921 | | Com_select | 109511 | | Com_select | 108084 | | Com_select | 108483 | | Com_select | 108115 | ...
[matsunobu@host ~]$ vmstat 1
r b swpd free buff cache in cs us sy id wa st 23 0 0 963004 224216 29937708 58242 163470 59 28 12 0 0 24 0 0 963312 224216 29937708 57725 164855 59 28 13 0 0 19 0 0 963232 224216 29937708 58127 164196 60 28 12 0 0 16 0 0 963260 224216 29937708 58021 165275 60 28 12 0 0 20 0 0 963308 224216 29937708 57865 165041 60 28 12 0 0
samples % app name symbol name 259130 4.5199 mysqld MYSQLparse(void*) 196841 3.4334 mysqld my_pthread_fastmutex_lock 106439 1.8566 libc-2.5.so _int_malloc 94583 1.6498 bnx2 /bnx 284550 1.4748 ha_innodb_plugin.so.0.0.0 ut_delay 67945 1.1851 mysqld _ZL20make_join_statisticsP4JOINP10TABLE_LISTP4ItemP16st_dynamic_array 63435 1.1065 mysqld JOIN::optimize() 55825 0.9737 vmlinux wakeup_stack_begin 55054 0.9603 mysqld MYSQLlex(void*, void*) 50833 0.8867 libpthread-2.5.so pthread_mutex_trylock 49602 0.8652 ha_innodb_plugin.so.0.0.0 row_search_for_mysql 47518 0.8288 libc-2.5.so memcpy 46957 0.8190 vmlinux .text.elf_core_dump 46499 0.8111 libc-2.5.so malloc
Parsing SQL statements 解析 SQL.
Opening, locking tables 打开并锁定表.
Making SQL execution plans SQL 执行计划.
Unlocking, closing tables 解锁并关闭表.
另外,MySQL 还必须要做大量的并发控制,比如在发送/接收网络数据包的时候,fcntl() 就要被调用很多次; Global mutexes 比如 LOCK_open,LOCK_thread_count 也被频繁地取得/释放。所以, 在 Oprofile 的输出中,排在第二位的是 my_pthread_fastmutex_lock()。并且 %system 占用的 CPU 相当高(28%)。
其实 MySQL 开发团队和外围的开发团体已意识到大量并发控制对性能的影响,MySQL5.5 中已经解决了一些问题。未来的 MySQL 版本中,应该会越来越好。
还有一个大的问题是,%user 达到了60%。互斥量的争夺导致 %system 上增,而不是 %user,即使 MySQL 内部关于互斥量的的问题都得到修复,还是很难达到我们所期望的 300,000 QPS.也许,会有人提到使用 HANDLER ,但是因为在解析 SQL时,opening/closing table 还是必须的,所以对于提高吞吐量,它还是只能爱莫能助。
如果只有一小部分数据进入内存,那么 SQL 带来的消耗可以忽略不计。很简单,因为磁盘的 I/0 操作所带来的消耗会要大,这种情况下时,就不需要太过的去考虑 SQL 所带来的消耗。
但是,在大多数的 hot MySQL 服务器中, 大部分的数据都是因为全部载入至内存中而变的只受 CPU 的限制。Profiling 的结果就类似上所述的那样: SQL 层消耗了大量的资源。假设,需要做大量的 PK 查询(i.e. SELECT x FROM t WHERE id=?)或者是做 LIMIT 的范围查询, 即使有 70-80% 都是在同一张表中做 PK 查询(仅仅只是查询条件中给定的值不同,即 value 不同而已), MySQL 还是每次需要去做 parse/open/lock/unlock/close, 这对我们来说,是非常影响效率的。
到底有没有好的方法来减少 MySQL SQL 层的 CPU 资源/争夺呢? 如果使用 MySQL Cluster, NDBAPI 不失为一个很好的解决办法。 在我还是 MySQL/Sun/Oracle 的顾问时,就见到过很多客户对SQL Node + NDB performance 感到非常不爽,但当他们用了 NDBAPI 客户端后,发现性能调提高了 N 倍。当然,在 MySQL Cluster 中是可以同时使用 NDBAPI 和 SQL 的,但在做频繁的访问模式时还是推荐使用 NDBAPI,而在 ad-hoc 或者 查询不频繁的情况下使用 SQL + MySQL + NDB。
以快捷的速度访问 API, 这正是我们需要的,但同时我们也想在 ad_hoc 或者复杂的查询的情况时还是使用 SQL. 像其他的 web service, DeNA 使用的是 InnoDB, 转为 NDB,这并不是一件容易的事情,因为内置InnoDB 即不支持 SQL 也不支持网络层的服务。
最好的办法可以是在 MySQL 的内部,实现一以 MySQL plugin 的形式存在的 NoSQL 的网络服务。它侦听在某端口来接收采用 NoSQL 协议/API 的通讯, 然后通过 MySQL 内部的存储引擎 API 来直接访问 InnoDB。这种方法的理念类似于 NDBAPI, 但是它可以做到与 InnoDB 通讯。
这个理念最初是去年由 Kazuho Oku 在 Cybozu Labs 上提出的,他曾写过采用 Memcached protocols 通讯的MyCached UDF。而我的大学同学实现了另外一个插件 ― HandlerSocket,
是以 MySQL daemaon plugin 形式存在,所以在应用中可把 MySQL 当 NoSQL 使用. 它最大的功能是实现了与存储引擎交互,比如 InnoDB,而这不需要任何的 SQL 方面的开销. 访问 MySQL 的 table 时,当然她也是需要 open/close table 的,但是 它并不是每次都去 open/close table, 因为它会将以前访问过的 table 保存下来以供来是使用,而 opening/closing tables 是最耗资源的,而且很容易引起互斥量的争夺,这样一来,对于提高性能,非常有效。在流量变小时, HandlerSocket 会 close tables, 所以,它不会阻塞 administrative commands (DDL).它与MySQL + Memcached 的区别在哪呢? 对比图1 和图2 ,可从中看出其不同点。图2 展示了典型的 MySQL + Memecached 的使用. 因为 Memcached 的 get 操作比 MySQL 的内存中/磁盘上的主键查询要快很多,所以 Memcached 用于缓存数据库记录。如果 HandlerSocket 的查询速度能和 Memcached 媲美,我们就不需要使用 Memcached 来缓存记录。
举一个例子,假设有一 user 表,通过 user_id 来获取用户信息:
CREATE TABLE user ( user_id INT UNSIGNED PRIMARY KEY, user_name VARCHAR(50), user_email VARCHAR(255), created DATETIME ) ENGINE=InnoDB;
用 SELECT 语句获取用户信息
mysql> SELECT user_name, user_email, created FROM user WHERE user_id=101; +---------------+-----------------------+---------------------+ | user_name | user_email | created | +---------------+-----------------------+---------------------+ | Yukari Takeba | yukari.takeba@dena.jp | 2010-02-03 11:22:33 | +---------------+-----------------------+---------------------+ 1 row in set (0.00 sec)
下面我们来看看如何使用 HandlerSocket 完成同样的事情.
安装 HandlerSocket
HandlerSocket具体安装步骤请参考这里,基本步骤如下:
1 下载HandlerSocket2 编译 HandlerSocket(客户端和服务端)
[root@localhost handlersocket]# ./configure --with-mysql-source=mysql-source-dir --with-mysql-bindir=mysql-server-bin-dir [root@localhost handlersocket]# make [root@localhost handlersocket]# make install
mysql> INSTALL PLUGIN \'handlersocket\' soname \'handlersocket.so\';
因为 HandlerSocket是 MySQL 插件,所以可以象使用其它插件,如 InnoDB, Q4M 和 Spider 那样使用它,即不需要修改 MySQL 源代码,MySQL 最好是 5.1 或更高版本,编译 HandlerSocket 时需要 MySQL 源码和 MySQL 库。
书写 HandlerSocket 客户端代码
目前已提供 C++ 和 perl 调用的客户端库,下面是使用 perl 调用的实例代码:
#!/usr/bin/perl use strict; use warnings; use Net::HandlerSocket; #1. establishing a connection my $args = { host => \'ip_to_remote_host\', port => 9998 }; my $hs = new Net::HandlerSocket($args); #2. initializing an index so that we can use in main logics. # MySQL tables will be opened here (if not opened) my $res = $hs->open_index(0, \'test\', \'user\', \'PRIMARY\', \'user_name,user_email,created\'); die $hs->get_error() if $res != 0; #3. main logic #fetching rows by id #execute_single (index id, cond, cond value, max rows, offset) $res = $hs->execute_single(0, \'=\', [ \'101\' ], 1, 0); die $hs->get_error() if $res->[0] != 0; shift(@$res); for (my $row = 0; $row < 1; ++$row) { my $user_name= $res->[$row + 0]; my $user_email= $res->[$row + 1]; my $created= $res->[$row + 2]; print "$user_name\\t$user_email\\t$created\\n"; } #4. closing the connection $hs->close()
#!/usr/bin/perl use strict; use warnings; use Net::HandlerSocket; #1. establishing a connection my $args = { host => \'ip_to_remote_host\', port => 9998 }; my $hs = new Net::HandlerSocket($args); #2. initializing an index so that we can use in main logics. # MySQL tables will be opened here (if not opened) my $res = $hs->open_index(0, \'test\', \'user\', \'PRIMARY\', \'user_name,user_email,created\'); die $hs->get_error() if $res != 0; #3. main logic #fetching rows by id #execute_single (index id, cond, cond value, max rows, offset) $res = $hs->execute_single(0, \'=\', [ \'101\' ], 1, 0); die $hs->get_error() if $res->[0] != 0; shift(@$res); for (my $row = 0; $row < 1; ++$row) { my $user_name= $res->[$row + 0]; my $user_email= $res->[$row + 1]; my $created= $res->[$row + 2]; print "$user_name\\t$user_email\\t$created\\n"; } #4. closing the connection $hs->close();
上面代码是通过 user_id=101 条件在 user 表获取用户 user_name, user_email和 created 信息,得到的结果应该和之前在 MySQL client 查询出来的结果一样。
[matsunobu@host ~]$ perl sample.pl Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33
对于大多数Web应用程序而言,保持轻量级的 HandlerSocket 连接是一个很好的做法(持续连接),让大量的请求可以集中于主要逻辑(上面代码中的#3部分)。
HandlerSocket 协议是一个小尺寸的基于文本的协议,和 Memcached 文本协议类似,可以使用 telnet 通过 HandlerSocket 获取数据。
[matsunobu@host ~]$ telnet 192.168.1.2 9998 Trying 192.168.1.2... Connected to xxx.dena.jp (192.168.1.2). Escape character is \'^]\'. P 0 test user PRIMARY user_name,user_email,created 0 1 0 = 1 101 0 3 Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33 (Green lines are request packets, fields must be separated by TAB)
绿色表示请求数据包,字段必须用Tab键分隔。
现在是时候展示基准测试结果,使用上面的 user 表,从多线程远程客户端测试了执行主键查询操作的次数,所有用户数据都装入到内存中(我测试了 100 万行),也用类似的数据测试了 Memcached(我使用 libMemcached 和 Memcached_get() 获取用户数据),在 MySQL SQL 测试中,我使用了的是传统的 SELECT 语句: “SELECT user_name, user_email, created FROM user WHERE user_id=?”, Memcached 和 HandlerSocket 客户端代码均使用 C/C++ 编写,所有客户端程序都位于远程主机上,通过 TCP/IP 连接到 MySQL/Memcached。最高的吞吐量情况如下:
approx qps server CPU util MySQL via SQL 105,000 %us 60% %sy 28% Memcached 420,000 %us 8% %sy 88% MySQL via HandlerSocket 750,000 %us 45% %sy 53%
HandlerSocket的吞吐量比使用传统 SQL 时高出 7.5, 而且 %us 也只有使用传统 SQL 时的3/4, 这说明 MySQL 的 SQL 层是非常耗资源的,如果能跳过这一层,性能肯定会大大提升。有趣的是,MySQL 使用 HandlerSocket 时的速度比使用 Memcached 也要快 178%,并且 Memcached 消耗的 %sy 资源也更多。所以虽然 Memcached 是一个很好的产品,但仍然有优化的空间。
下面是oprofile输出内容,是在 MySQL HandlerSocket 测试期间收集到的,在核心操作,如网络数据包处理,获取数据等的 CPU 资源消耗(bnx2是一个网络设备驱动程序)。
samples % app name symbol name 984785 5.9118 bnx2 /bnx2 847486 5.0876 ha_innodb_plugin.so.0.0.0 ut_delay 545303 3.2735 ha_innodb_plugin.so.0.0.0 btr_search_guess_on_hash 317570 1.9064 ha_innodb_plugin.so.0.0.0 row_search_for_mysql 298271 1.7906 vmlinux tcp_ack 291739 1.7513 libc-2.5.so vfprintf 264704 1.5891 vmlinux .text.super_90_sync 248546 1.4921 vmlinux blk_recount_segments 244474 1.4676 libc-2.5.so _int_malloc 226738 1.3611 ha_innodb_plugin.so.0.0.0 _ZL14build_template P19row_prebuilt_structP3THDP8st_tablej 206057 1.2370 HandlerSocket.so dena::hstcpsvr_worker::run_one_ep() 183330 1.1006 ha_innodb_plugin.so.0.0.0 mutex_spin_wait 175738 1.0550 HandlerSocket.so dena::dbcontext:: cmd_find_internal(dena::dbcallback_i&, dena::prep_stmt const&, ha_rkey_function, dena::cmd_exec_args const&) 169967 1.0203 ha_innodb_plugin.so.0.0.0 buf_page_get_known_nowait 165337 0.9925 libc-2.5.so memcpy 149611 0.8981 ha_innodb_plugin.so.0.0.0 row_sel_store_mysql_rec 148967 0.8943 vmlinux generic_make_request
因为 HandlerSocket 是运行于 MySQL 内部,直接与 InnoDB 交互,所以,可以使用常见的 SQL 命令,如 SHOW GLOBAL STATUS 获得统计信息,Innodb_rows_read 达到了 750000+ 是值得一看的。
$ mysqladmin extended-status -uroot -i 1 -r | grep "InnoDB_rows_read" ... | Innodb_rows_read | 750192 | | Innodb_rows_read | 751510 | | Innodb_rows_read | 757558 | | Innodb_rows_read | 747060 | | Innodb_rows_read | 748474 | | Innodb_rows_read | 759344 | | Innodb_rows_read | 753081 | | Innodb_rows_read | 754375 | ...
测试用机的详细信息如下:
型号 | 戴尔PowerEdge R710 |
---|---|
CPU | Nehalem 8核,E5540@2.53GHz |
内存 | 32GB(所有数据都装入缓冲池) |
MySQL | 5.1.50 InnoDB |
Memcached/libMemcached | 1.4.5(Memcached),0.44(libMemcached) |
Network | Boradcom NetXtreme II BCM5709 1000Base-T(内建四端口,使用了其中三个) |
Memcached 和 HandlerSocket 都做了网络 I/O 限制,当我测试单个端口时,HandlerSocket 的 QPS 为 260000,而 Memcached 为 220000。
如下所述,HandlerSocket 有其自己的特点和优势,而其中一些对我们来说, 是真的很给力.
支持多种查询模式
HandlerSocket 目前支持 主键/唯一性查询,非唯一性索引查询,范围扫描,LIMIT 和 INSERT/UPDATE/DELETE,但还不支持未使用任何索引的操作。另外,multi_get()(类似于in(1,2,3), 只需一次网络往返)还可获取多行数据。到这里可查询详细信息。
处理大量并发连接
HandlerSocket 连接是轻量级的,因为 HandlerSocket 采用epoll()和 worker-thread/thread-pooling 架构,而 MySQL 内部线程的数量是有限的(可以由 my.cnf中的 handlersocket_threads参数控制),所以即使建立上千万的网络连接到 HandlerSocket,它的稳定性也不会受到任何影响(消耗太多的内存,会造成巨大的互斥竞争等其他问题,如bug#26590,bug#33948,bug#49169)。
及其优秀的性能
HandlerSocket,如上所描述, 相对于其它 NoSQL 阵容,性能表现一点也不逊色。事实上,我还未曾见过哪个 NoSQL 产品在一台普通服务器上可达到 750000+ 次查询。它不仅没有调用与 SQL 相关的函数,还优化了网络/并发相关的问题。
更小的网络数据包和传统 MySQL 协议相比,HandlerSocket 协议更简短,因此整个网络的流量更小。
运行有限的 MySQL 内部线程数参考上面的内容。
将客户端请求分组当大量的并发请求抵达 HandlerSocket 时,每个工作线程尽可能多地聚集请求,然后同时执行聚集起来的请求和返回结果。这样,通过牺牲一点响应时间,而大大地提高性能。例如,你可以得到以下好处,如果有人感兴趣,我会在今后的文章中对它们加以深入的解释。
减少fsync()调用的次数.
减少复制延迟.
无重复缓存
当使用 Memcached 缓存 MySQL/InnoDB 记录时,在 Memcached 和 InnoD B缓冲池中均缓存了这些记录,因此效率非常低(内存仍然很贵). 而采用 HandlerSocket插件, 由于它访问 InnoDB 存储引擎,记录缓存在 InnoDB 缓冲池中,这样,其它 SQL 语句就可以重复使用它。
无数据不一致的现象
由于数据只存储在一个地方(InnoDB 内),不像使用 Memcached 时,需要在 Memcached 和 MySQL 之间检查数据一致性。
崩溃安全
后端存储是 InnoDB,它是事务性和崩溃安全的,即使有设置innodb-flush-log-at-trx-commit!=1,在服务器崩溃时也只会丢掉
可从 MySQL 客户端使用 SQL
在许多情况下,人们仍然希望使用 SQL(如生产摘要报告),这就是为什么我们不能使用嵌入式 InnoDB 的原因,大多数 NoSQL 产品都不支持 SQL 接口,HandlerSocket 仅仅是一个 MySQL 插件,可以从 MySQL 客户端发送 SQL 语句,但当需要高吞吐量时,最好使用 HandlerSocket。
从 MySQL获益
因为 HandlerSocket 运行于 MySQL 内部,因此所有 MySQL 操作,如 SQL,在线备份,复制,通过 Nagios/EnterpriseMonitor 监控等都是支持的,HandlerSocket 获得可以通过普通的 MySQL 命令监控,如SHOW GLOBAL STAUTS,SHOW ENGINE INNODB STATUS和SHOW PROCESSLIST等.
不需要修改/重建 MySQL
因为 HandlerSocket 是一个插件,所以它支持 MySQL 社区版和企业服务器版,而无需对 MySQL 做出任何修改就可以使用。
独立于存储引擎
虽然我们只测试了5.1和5.5 InnoDB 插件,但 HandlerSocket 可以和任何存储引擎交互。
需要学习HandlerSocket API
尽管它很容易使用,但仍然需要学习如何与 HandlerSocket 交互,我们提供了C++ API、Perl绑定。
没有安全功能
和其它NoSQL数据库类似,HandlerSocket不支持安全功能,HandlerSocket的工作线程以系统用户权限运行,因此应用程序可以访问通过 HandlerSocket 协议的所有表,当然,你可以象其它 NoSQL 产品一样使用防火墙过滤数据包。
对于 HDD 绑定工作负载没有优势
对于 HDD I/O 绑定工作负载,数据库每秒无法执行数千次查询,通常只有 1-10% 的 CPU 利用率,在这种情况下,SQL 执行层不会成为瓶颈,因此使用HandlerSocket没有什么优势,我们只在数据完全装载到内存的服务器上使用 HandlerSocket。
我们已经在生产环境中使用了 HandlerSocket 插件,效果很明显,因为我们减少了许多 Memcached 和 MySQL 从属服务器,而且整个网络流量也在减少。目前还没有发现任何性能问题(如响应时间慢,延迟等)。
我认为, NoSQL/Database 社区完全低估了 MySQL, 相对于其他产品来说,它历史悠久,而且到目前为止,我优秀的前同事们也做了许多独特的、伟大的改进。从 NDBAPI 可以看出 MySQL 有成为 NoSQL 的潜力,因为存储引擎 API 和守护进程接口的完全独立,使得 Akira 和 DeNA 开发 HandlerSocket 成为可能。作为 MySQL 一名前员工和对 MySQL 长期的了解,我想看到 MySQL 变得更好,更受欢迎,而不仅仅只作为一个 RDBMS,也应该成为 NoSQL 阵营中的一员。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

本文探讨了Docker中的优化MySQL内存使用量。 它讨论了监视技术(Docker统计,性能架构,外部工具)和配置策略。 其中包括Docker内存限制,交换和cgroups

本文介绍了MySQL的“无法打开共享库”错误。 该问题源于MySQL无法找到必要的共享库(.SO/.DLL文件)。解决方案涉及通过系统软件包M验证库安装

本文讨论了使用MySQL的Alter Table语句修改表,包括添加/删除列,重命名表/列以及更改列数据类型。

本文比较使用/不使用PhpMyAdmin的Podman容器直接在Linux上安装MySQL。 它详细介绍了每种方法的安装步骤,强调了Podman在孤立,可移植性和可重复性方面的优势,还

本文提供了SQLite的全面概述,SQLite是一个独立的,无服务器的关系数据库。 它详细介绍了SQLite的优势(简单,可移植性,易用性)和缺点(并发限制,可伸缩性挑战)。 c

本指南展示了使用自制在MacOS上安装和管理多个MySQL版本。 它强调使用自制装置隔离安装,以防止冲突。 本文详细详细介绍了安装,起始/停止服务和最佳PRA

文章讨论了为MySQL配置SSL/TLS加密,包括证书生成和验证。主要问题是使用自签名证书的安全含义。[角色计数:159]

文章讨论了流行的MySQL GUI工具,例如MySQL Workbench和PhpMyAdmin,比较了它们对初学者和高级用户的功能和适合性。[159个字符]
