首页
搜索
1
招聘PHP开发工程师哪个平台更专业?
7 阅读
2
html5网页制作模板 网页模板html下载
4 阅读
3
蒋易孙天宇语言天赋藏不住了
4 阅读
4
PHP执行SQL语句方法
4 阅读
5
【开发】NekoIPinfo:基于GO语言的高性能IP查询服务端
3 阅读
技术分享
源码分类
工具分享
采集专区
登录
搜索
私人云
累计撰写
200
篇文章
累计收到
0
条评论
首页
栏目
技术分享
源码分类
工具分享
采集专区
页面
搜索到
199
篇与
的结果
2026-03-20
开发者呼吁甲骨文释放MySQL控制权避免技术停滞
数据库老手和开发者社区正敦促甲骨文放松对MySQL的控制来自数据库领域的资深人士、开发者和长期贡献者正在向甲骨文施压,要求该公司将这个开源数据库转移至独立基金会模式管理。这一呼吁通过公开信的形式表达,反映出人们对MySQL开发速度、路线图透明度以及在日益AI驱动的数据生态系统中作用的日益担忧。该公开信已获得至少248个签名支持。签名者涵盖了来自MySQL分支提供商如Percona、MariaDB和PlanetScale的数据库管理员、架构师和开发者,以及来自Zoho、DigitalOcean、Vultr和Pinterest等公司的工程师和高管。MySQL市场份额流失成关键担忧签名者主要担心甲骨文对MySQL代码库更新的管理方式,他们认为这已经导致该数据库失去了大量市场份额。随着对AI驱动工作负载需求的激增,开发者和企业正越来越多地转向PostgreSQL,在这些场景中数据库在整合和服务数据方面发挥着关键作用。公开信还指出,MySQL的更新不仅"私有化"且稀少,甚至缺乏现在AI驱动工作负载的基本功能,这些功能已经成为大多数数据库的标准配置,包括甲骨文提供的企业版本。Percona联合创始人Vadim Tkachenko是这封信的联合作者之一,他告诉InfoWorld,企业对甲骨文治下MySQL发展方向的担忧已达到"临界"水平。企业现在正寻求MySQL分支提供商和云提供商如AWS来获取新功能和创新,这反映出他们认为核心MySQL项目存在停滞。不过Tkachenko指出,企业的兴趣加上分支和云提供商层面的创新并不能帮助MySQL向前发展,反而创造了混乱和碎片化:"分支之间以及与上游(核心开源MySQL)之间往往不兼容,这为采用和迁移创造了重大障碍。"分析师认同这一观点HyperFRAME Research的AI技术栈负责人Stephanie Walter表示:"公开信中关于MySQL开发速度和治理的担忧与我所观察到的情况一致。数据库层正成为AI系统的依赖项。当开发者和企业感觉上游在现代需求方面缓慢或不透明时,他们不只是抱怨,而是会绕过它,很可能转向PostgreSQL这样的替代方案。"dbinsight首席分析师Tony Baer呼应了Walter的观点,指出MySQL分支确实会因其独特的个别扩展而创造锁定效应,导致迁移方面的挑战。独立基金会模式成为解决方案尽管如此,Tkachenko和其他签名者确实看到了拯救MySQL摆脱所谓困境的替代方案:甲骨文接受将MySQL置于独立非营利基金会的提议。根据公开信中概述的提议模式,MySQL将由一个中立的非营利基金会治理,设立技术指导委员会,代表甲骨文、分支提供商、云供应商和更广泛的贡献者社区。该基金会将监督路线图规划、发布治理和贡献者访问权限,同时允许甲骨文保留其商业MySQL产品和商标。签名者认为,这种结构既能保护甲骨文的商业利益,也能通过透明的路线图、更新和长期技术方向给供应商和企业更多信心,同时减少分支间的碎片化。然而,分析师对公开信中提议的基金会模式似乎不太有信心。Walter表示:"如果甲骨文保留商标和实际的发布管道,这并不能完全解决核心权力动态问题。"她补充说,提议的模式可能有助于协调和贡献过程——这是签名者表达的另一个担忧。Walter进一步解释说,提议的结构与PostgreSQL等自主的社区主导项目形成对比,PostgreSQL的治理模式在维持贡献者信任和加速长期采用方面发挥了重要作用。根据2025年Stack Overflow调查,PostgreSQL在使用率和受欢迎程度方面领先于MySQL。贡献者数量急剧下降MySQL受欢迎程度的下降可能直接与过去几年贡献者和提交数量的急剧下降有关。Percona的软件工程经理Julia Vural在一篇博客文章中写道,MySQL的活跃贡献者池到2025年第三季度已降至约75人,而2017年第四季度时有135名活跃贡献者。同样,提交总数也从2010年的22,360次下降到2024年的4,730次。其他因素可能包括甲骨文MySQL部门最近的裁员,包括甲骨文MySQL社区经理Frederic Descamps的离职,他本周转到了MariaDB基金会。甲骨文尚未立即回应关于这封公开信的询问。Q&AQ1:为什么开发者要求甲骨文释放对MySQL的控制权?A:开发者担心MySQL在甲骨文管理下开发速度缓慢、缺乏透明度,更新稀少且缺乏AI工作负载所需的基本功能。这导致MySQL市场份额流失,企业和开发者转向PostgreSQL等替代方案。Q2:独立基金会模式如何解决MySQL的问题?A:提议的模式下,MySQL将由中立的非营利基金会治理,设立技术指导委员会代表各方利益。这样既能保护甲骨文商业利益,又能通过透明的路线图和治理给市场更多信心,减少分支碎片化。Q3:MySQL的贡献者数量发生了什么变化?A:MySQL的活跃贡献者从2017年第四季度的135人降至2025年第三季度的约75人。提交总数也从2010年的22,360次下降到2024年的4,730次,显示出明显的衰退趋势。返回搜狐,查看更多
2026年03月20日
0 阅读
0 评论
0 点赞
2026-03-20
零基础学MySQL必看!这本经典教材50万学子都在用!
想入门 MySQL 数据库,却怕知识点杂乱、学不会实操?别慌!今天给大家推荐一本超靠谱的宝藏教材 ——《MySQL 数据库入门(第3版)》,不管是计算机专业学生、数据库培训学员,还是想转行做开发的小白,有了它,轻松搞定 MySQL!编辑推荐先说说这本书的 “硬实力”!它可是新工科计算机类专业教材,300 多所高校、50 余万学子都在用,经典程度不言而喻!编著方黑马程序员在 IT 教育领域超有经验,教材质量有保障,完全不用担心踩坑~再看内容,真的太适合零基础了!全书共 10 章,从基础到进阶,循序渐进帮你掌握 MySQL。第 1 章先讲数据库入门知识,从数据管理技术发展、基本术语,到数据模型、SQL 简介,还详细教你 MySQL 的安装配置和图形化管理工具使用,就算是小白,跟着步骤也能轻松上手。第 2 - 5 章聚焦基础操作,数据库和数据表的创建修改、数据增删改查、单表与多表查询,每个知识点都有实用案例,帮你打牢基础。第 6 - 9 章深入高级知识,索引、视图、事务、数据库编程、管理维护,这些在实际开发中超重要的技能,书中都讲得通俗易懂,让你从 “会用” 进阶到 “活用”。最实用的是第 10 章,通过 Java Web 项目讲解 MySQL 在实际项目中的应用,帮你打通 “理论 - 实操 - 项目” 的链路,学完就能上手做真实开发!这本书的特色真的戳中学习者痛点!首先是体系完整,基于 MySQL 8.4.3 版本更新,知识点全面且系统,不会让你学完还一头雾水。其次是实操性强,真实项目贯穿全书,上机实践还融入了 AIGC 技术,学习体验超新颖,能循序渐进培养实操技能。配套资源也超丰富!为教师提供教学大纲、教学设计、课件、源代码、教学视频和题库,方便教学;为学习者提供在线答疑,遇到问题能及时解决,再也不用怕卡壳。语言更是通俗易懂,简洁精练,零基础也能轻松理解。定价 59.9 元,就能拥有一本被众多高校认可、实操性强、配套完善的经典教材,不管是上课学习、培训提升,还是自学入门,都超值!别犹豫啦,赶紧入手,一起开启 MySQL 数据库的学习之旅,为编程之路打下坚实基础!返回搜狐,查看更多
2026年03月20日
0 阅读
0 评论
0 点赞
2026-03-20
为啥会这样?!MySQL并行复制竟然比单线程慢?
作者介绍陈臣,甲骨文 MySQL 数据库专家,《MySQL实战》作者,有超过10年的数据库管理与架构经验,对 MySQL、Redis 源码略有研究。最近碰到一个case,发现在特定场景下,并行复制竟然比单线程复制要慢。一、现象从某个时间点开始,从库的复制延迟持续增加,且没有下降的趋势。数据库版本:8.0.40,事务隔离级别 RC(Read Committed),并行重放线程数(replica_parallel_workers)为 8。二、分析过程通过show slave status\G查看,发现Relay_Master_Log_File和Exec_Master_Log_Pos都在变化,只不过变化得比较慢。刚开始怀疑是主库写入量较大导致的,后来通过mysql-binlog-time-extractor(具体用法可参考:分享一个 MySQL binlog 分析小工具)分析,发现主库的写入量在刚开始出现延迟时(2025-09-01 09:30)并不大,反倒是写入量大的时间段(2025-09-01 04:57:53 - 2025-09-01 05:02:42)没有出现延迟。+--------------------+------------------------+---------------------+---------------------+-----------+------------------------------------+ Log_name | File_size | Start_time | End_time | Duration | GTID | +--------------------+------------------------+---------------------+---------------------+-----------+------------------------------------+ binary-log.005565 | 1302499830 (1.21 GB) | 2025-09-01 04:57:53 | 2025-09-01 04:58:22 | 00:00:29 | 1284696693-1284699126 | binary-log.005566 | 1105002721 (1.03 GB) | 2025-09-01 04:58:22 | 2025-09-01 04:58:23 | 00:00:01 | 1284699127-1284699312 | binary-log.005567 | 1273545902 (1.19 GB) | 2025-09-01 04:58:23 | 2025-09-01 05:02:33 | 00:04:10 | 1284699313-1284728539 | binary-log.005568 | 1287820910 (1.20 GB) | 2025-09-01 05:02:33 | 2025-09-01 05:02:42 | 00:00:09 | 1284728540-1284729282 | ... binary-log.005633 | 58514304 (55.80 MB) | 2025-09-01 09:12:53 | 2025-09-01 09:17:53 | 00:05:00 | 1286735216-1286786118 | binary-log.005634 | 58955596 (56.22 MB) | 2025-09-01 09:17:53 | 2025-09-01 09:22:53 | 00:05:00 | 1286786119-1286834568 | binary-log.005635 | 71508778 (68.20 MB) | 2025-09-01 09:22:53 | 2025-09-01 09:27:53 | 00:05:00 | 1286834569-1286880281 | binary-log.005636 | 107107179 (102.15 MB) | 2025-09-01 09:27:53 | 2025-09-01 09:32:53 | 00:05:00 | 1286880282-1286942223 | binary-log.005637 | 530205055 (505.64 MB) | 2025-09-01 09:32:53 | 2025-09-01 09:37:53 | 00:05:00 | 1286942224-1287246612 | binary-log.005638 | 546754562 (521.43 MB) | 2025-09-01 09:37:53 | 2025-09-01 09:42:53 | 00:05:00 | 1287246613-1287562930 | binary-log.005639 | 528677634 (504.19 MB) | 2025-09-01 09:42:53 | 2025-09-01 09:47:53 | 00:05:00 | 1287562931-1287868985 | +--------------------+------------------------+---------------------+---------------------+-----------+------------------------------------+ 查看该实例的错误日志,发现有大量的锁等待超时报错。需要注意的是,这个实例的事务隔离级别是 RC。在该级别下,MySQL 通常只会加记录锁。此外,该实例启用了 WRITESET 并行复制,MySQL 会根据事务修改的主键或唯一索引来判断是否可并行执行。换句话说,如果两个事务在主键或唯一索引上存在冲突,它们将无法并行重放。理论上,在这种机制组合下,从库在重放过程不应发生锁等待超时。随后使用binlog_summary.py对延迟开始时段的四个 binlog 文件( binary-log.005636 ~ binary-log.005639 )进行了分析,发现这些 binlog 的操作模式十分相似:操作次数排名前两位的均为同一张表biz_schema.tbl_product_service_mapping01的 DELETE 与 INSERT 操作。# python3 binlog_summary.py -f binary-log.005636.txt -c opr --new TABLE_NAME DML_TYPE NUMS biz_schema.tbl_product_service_mapping01 INSERT 71271 biz_schema.tbl_product_service_mapping01 DELETE 67434 ... 写了个简单的脚本测试了下,发现对于相同的唯一索引值,INSERT操作总是出现在对应的DELETE操作之后,于是写了个脚本将 DELETE操作涉及的记录提取出来并插入到测试库中,然后将相关 binlog 当作 relay log 进行重放。为了排除其它表的干扰,在重放时设置了replicate-do-table = biz_schema.tbl_product_service_mapping01,只重放这一张表。下面是具体的重放步骤:1、初始化 relay log:CHANGE MASTER TO MASTER_HOST=dummy; STOP SLAVE; RESET SLAVE ALL; 执行上述命令后,MySQL 会在当前数据目录下生成两个文件: instance-20250903-0701-relay-bin.000001(第一个 relay log 文件) instance-20250903-0701-relay-bin.index(relay log 索引文件)其中,instance-20250903-0701 是主机名。2、替换掉 relay log:用 binary-log.005636 替换掉 instance-20250903-0701-relay-bin.000001,并修改该文件的属主。# cp binary-log.005636 /data/mysql/3306/data/instance-20250903-0701-relay-bin.000001 # chown mysql.mysql /data/mysql/3306/data/instance-20250903-0701-relay-bin.000001 3、启动 SQL 线程进行重放:CHANGE MASTER TO RELAY_LOG_FILE=instance-20250903-0701-relay-bin.000001, RELAY_LOG_POS=1, MASTER_HOST=dummy; START SLAVE SQL_THREAD; 结果发现能成功重放,且重放过程中未出现任何报错。测试了三次,重放时间分别为 362.74s、352.69s、361.75s,平均耗时 359.06 秒。每次重放过程中,错误日志中都出现了多次锁等待超时错误。2025-09-21T07:53:45.257279-00:00 260 [Warning] [MY-010584] [Repl] Slave SQL for channel : Worker 5 failed executing transaction 9206ff59-2d95-4a02-88cf-04d97adfdd65:1286917678 at master log , end_log_pos 63251784; Could not execute Write_rows event on table biz_schema.tbl_product_service_mapping01; Lock wait timeout exceeded; try restarting transaction, Error_code: 1205; handler error HA_ERR_LOCK_WAIT_TIMEOUT; the events master log FIRST, end_log_pos 63251784, Error_code: MY-001205 很显然,锁等待超时是并行重放导致的。如果是单线程重放,就能规避这个问题,于是将replica_parallel_workers设置为 1,重新执行相同的测试,三次重放时间分别为 82.39s、83.40s、83.43s,平均仅 83.07 秒。想不到,单线程重放竟然比多线程快了四倍多。接下来,重点分析下锁等待超时问题。三、为什么会出现锁等待?以下是出现锁等待时,sys.innodb_lock_waits的输出:mysql> select * from sys.innodb_lock_waits\G *************************** 1.row *************************** wait_started: 2025-10-1213:11:29 wait_age: 08:00:33 wait_age_secs: 28833 locked_table: `biz_schema`.`tbl_product_service_mapping01` locked_table_schema: biz_schema locked_table_name: tbl_product_service_mapping01 locked_table_partition: NULL locked_table_subpartition: NULL locked_index: tbl_product_service_pk locked_type: RECORD waiting_trx_id: 5221288 waiting_trx_started: 2025-10-1213:11:22 waiting_trx_age: 08:00:40 waiting_trx_rows_locked: 35 waiting_trx_rows_modified: 34 waiting_pid: 10 waiting_query: INSERT IGNORE INTO tbl_product ... (10512475, 1073743289) , waiting_lock_id: 140256432120808:10011:67:240:263:140256317861168 waiting_lock_mode: X,GAP,INSERT_INTENTION blocking_trx_id: 5221291 blocking_pid: 14 blocking_query: INSERT IGNORE INTO tbl_product ... (10512476, 1073743289) blocking_lock_id: 140256432125848:9282:67:240:263:140256317891856 blocking_lock_mode: S,GAP blocking_trx_started: 2025-10-1213:11:22 blocking_trx_age: 08:00:40 blocking_trx_rows_locked: 35 blocking_trx_rows_modified: 34 sql_kill_blocking_query: KILL QUERY 14 sql_kill_blocking_connection: KILL 14 *************************** 2.row *************************** wait_started: 2025-10-1213:11:29 wait_age: 08:00:33 wait_age_secs: 28833 locked_table: `biz_schema`.`tbl_product_service_mapping01` locked_table_schema: biz_schema locked_table_name: tbl_product_service_mapping01 locked_table_partition: NULL locked_table_subpartition: NULL locked_index: tbl_product_service_pk locked_type: RECORD waiting_trx_id: 5221291 waiting_trx_started: 2025-10-1213:11:22 waiting_trx_age: 08:00:40 waiting_trx_rows_locked: 35 waiting_trx_rows_modified: 34 waiting_pid: 14 waiting_query: INSERT IGNORE INTO tbl_product ... (10512476, 1073743289) waiting_lock_id: 140256432125848:9282:67:240:260:140256317892560 waiting_lock_mode: X,GAP,INSERT_INTENTION blocking_trx_id: 5221289 blocking_pid: 12 blocking_query: NULL blocking_lock_id: 140256432123832:9816:67:240:260:140256317879376 blocking_lock_mode: S,GAP blocking_trx_started: 2025-10-1213:11:22 blocking_trx_age: 08:00:40 blocking_trx_rows_locked: 34 blocking_trx_rows_modified: 33 sql_kill_blocking_query: KILL QUERY 12 sql_kill_blocking_connection: KILL 12 2rowsinset (0.01 sec) 可以看出: PID 10 的 INSERT 操作被 PID 14 持有的 S,GAP 锁阻塞。 PID 14的 INSERT 操作又被 PID 12 持有的 S,GAP 锁阻塞。使用performance_schema.data_locks可以获取更详细的锁信息,包括被锁定的数据行:SELECT w.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx_id, w.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx_id, l1.LOCK_MODE AS waiting_lock_mode, l1.LOCK_DATA AS waiting_lock_data, l2.LOCK_MODE AS blocking_lock_mode, l2.LOCK_DATA AS blocking_lock_data FROM performance_schema.data_lock_waits AS w JOIN performance_schema.data_locks AS l1 ON w.REQUESTING_ENGINE_LOCK_ID = l1.ENGINE_LOCK_ID JOIN performance_schema.data_locks AS l2 ON w.BLOCKING_ENGINE_LOCK_ID = l2.ENGINE_LOCK_ID; +----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+ waiting_trx_id | blocking_trx_id | waiting_lock_mode | waiting_lock_data | blocking_lock_mode | blocking_lock_data | +----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+ 5221288 | 5221291 | X,GAP,INSERT_INTENTION | 10512476, 1, 18158557178 | S,GAP | 10512476, 1, 18158557178 | 5221291 | 5221289 | X,GAP,INSERT_INTENTION | 10512477, 1, 18158557146 | S,GAP | 10512477, 1, 18158557146 | +----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+ 2 rows in set (0.00 sec) 接下来从show processlist的输出中看看 PID 10、14、12 这三个线程的状态。mysql> show processlist; +----+-----------------+-----------+-----------+---------+---------+---------------------------------------------+------------------------------------------------------------------------------------------------------+ Id | User | Host | db | Command | Time | State | Info | +----+-----------------+-----------+-----------+---------+---------+---------------------------------------------+------------------------------------------------------------------------------------------------------+ 5 | event_scheduler | localhost | NULL | Daemon | 1813 | Waiting on empty queue | NULL | 8 | root | localhost | biz_schema | Query | 0 | init | show processlist | 9 | system user | | NULL | Query | 1588 | Waiting for dependent transaction to commit | NULL | 10 | system user | | biz_schema | Query | 3585949 | Applying batch of row changes (write) | INSERT IGNORE INTO tbl_product_service_mapping01 ( c1, c2 | 11 | system user | | NULL | Query | 3585949 | Waiting for preceding transaction to commit | NULL | 12 | system user | | NULL | Query | 3585949 | Waiting for preceding transaction to commit | NULL | 13 | system user | | NULL | Query | 3585949 | Waiting for preceding transaction to commit | NULL | 14 | system user | | biz_schema | Query | 3585949 | Applying batch of row changes (write) | INSERT IGNORE INTO tbl_product_service_mapping01 ( c1, c2 | 15 | system user | | NULL | Query | 3585949 | Waiting for preceding transaction to commit | NULL | 16 | system user | | NULL | Query | 3585949 | Waiting for an event from Coordinator | NULL | 17 | system user | | NULL | Query | 3585949 | Waiting for an event from Coordinator | NULL | +----+-----------------+-----------+-----------+---------+---------+---------------------------------------------+------------------------------------------------------------------------------------------------------+ 11rowsinset, 1warning (0.00 sec) PID 12 的执行用户是system user,说明它是并行重放的工作线程,其状态为Waiting for preceding transaction to commit,表示该线程正在等待它前面的事务提交完成。而 PID 10 和 PID 14 的状态均为Applying batch of row changes (write)。从字面上看,似乎是在执行批量写入操作,但实际上,这两个线程正在等待锁。如果执行的是SHOW FULL PROCESSLIST,Info列的INSERT IGNORE操作中还可以看到具体要插入的唯一索引值。借助这些唯一索引值,可以在 binlog 中精确定位对应的执行位置,便于分析事务执行顺序和锁等待情况。不过,对于 PID 12,由于Info列为NULL,无法直接看到具体的 DML 操作,因此难以定位其执行内容。为了解决这个问题,我在Slave_worker::slave_worker_exec_event函数中,在调用ev->do_apply_event_worker(this)的前后分别添加了日志打印。这样,就能清楚地看到每个工作线程正在执行的 event 的 binlog 位置点信息。int Slave_worker::slave_worker_exec_event(Log_event *ev) { ... ulong thread_id = thd->thread_id(); ulong log_pos = static_cast<ulong>(ev->common_header->log_pos); std::string msg = "Executing event: worker_thread_id=" + std::to_string(thread_id) + ", master_log_pos=" + std::to_string(log_pos); LogErr(INFORMATION_LEVEL, ER_CONDITIONAL_DEBUG, msg.c_str()); ret = ev->do_apply_event_worker(this); msg = "Done executing event: worker_thread_id=" + std::to_string(thread_id) + ", master_log_pos=" + std::to_string(log_pos); LogErr(INFORMATION_LEVEL, ER_CONDITIONAL_DEBUG, msg.c_str()); return ret; } 下面是锁等待发生时,PID 10、12、14 正在执行的 binlog event 位置点信息:# PID 10 grep worker_thread_id=10 /data/mysql/3306/data/mysqld.err | tail -1 2025-10-12T13:11:22.639120-00:00 10 [Note] [MY-013935] [Repl] Executing event: worker_thread_id=10, master_log_pos=63245428 # PID 12 grep worker_thread_id=12 /data/mysql/3306/data/mysqld.err | tail -1 2025-10-12T13:11:22.725638-00:00 12 [Note] [MY-013935] [Repl] Executing event: worker_thread_id=12, master_log_pos=63248672 # PID 14 grep worker_thread_id=14 /data/mysql/3306/data/mysqld.err | tail -1 2025-10-12T13:11:22.646870-00:00 14 [Note] [MY-013935] [Repl] Executing event: worker_thread_id=14, master_log_pos=63251784 可以看到,PID 10 对应的事务在 binlog 中的位置早于 PID 12。当参数replica_preserve_commit_order设置为 ON 时,从库必须严格按照主库的提交顺序依次提交事务,因此 PID 12 必须等待 PID 10 提交完成才能继续执行。结合锁依赖关系,就形成了一个循环等待的局面: PID 10 等待 PID 14 持有的 S,GAP 锁; PID 14 等待 PID 12 持有的 S,GAP 锁; PID 12 因提交顺序限制,必须等待 PID 10 提交事务。最终,这种环路导致三个线程相互阻塞,直到锁等待超时,MySQL 才会重新执行这些事务。四、模拟从库的重放操作根据获取到的 PID 10、12、14 对应的 event 位置点信息,我们可以还原出锁等待发生时这三个线程正在执行的具体操作:worker_thread_id=10 master_log_pos=63245428: insert c1 = 10512475 的所有记录,如(10512475,1),(10512475,20)... worker_thread_id=12 master_log_pos=63248672: insert c1 = 10512477 的所有记录,如(10512477,1),(10512477,20)... worker_thread_id=14 master_log_pos=63251784: insert c1 = 10512476 的所有记录,如(10512476,1),(10512476,20)... 这里的记录值对应的是表的联合唯一索引,其中c1是联合索引的第一列。值得注意的是,在这些INSERT操作之前,binlog 中还存在针对相同c1值的DELETE操作:delete c1 = 10512475 的所有记录,如(10512475,1),(10512475,20)... delete c1 = 10512477 的所有记录,如(10512477,1),(10512477,20)... delete c1 = 10512476 的所有记录,如(10512476,1),(10512476,20)... 也就是说,业务实际上是通过 DELETE + INSERT 的方式实现数据更新。为了进一步分析锁等待问题,我打印了重放过程中每个INSERT操作的具体内容。2025-10-12T13:11:22.639267-00:00 10 [Note] [MY-013935] [Repl] Inserted row: (18158557112, 10512475, 1, ...) 2025-10-12T13:11:22.640729-00:00 10 [Note] [MY-013935] [Repl] Inserted row: (18158557113, 10512475, 20, ...) 2025-10-12T13:11:22.642004-00:00 10 [Note] [MY-013935] [Repl] Inserted row: (18158557114, 10512475, 26, ...) 2025-10-12T13:11:22.643344-00:00 10 [Note] [MY-013935] [Repl] Inserted row: (18158557115, 10512475, 123, ...) 2025-10-12T13:11:22.644262-00:00 12 [Note] [MY-013935] [Repl] Inserted row: (18158557146, 10512477, 1, ...) 2025-10-12T13:11:22.644663-00:00 10 [Note] [MY-013935] [Repl] Inserted row: (18158557116, 10512475, 131, ...) 2025-10-12T13:11:22.646250-00:00 12 [Note] [MY-013935] [Repl] Inserted row: (18158557147, 10512477, 20, ...) 2025-10-12T13:11:22.647020-00:00 14 [Note] [MY-013935] [Repl] Inserted row: (18158557178, 10512476, 1, ...) 2025-10-12T13:11:22.647192-00:00 10 [Note] [MY-013935] [Repl] Inserted row: (18158557117, 10512475, 133, ...) 其中,第一个值是自增主键,后两个值是唯一索引列。从输出可以看到,这三个事务的插入操作是交叉执行的。模拟从库重放过程下面通过一个实验来模拟从库的重放操作。首先,在会话 1 中创建测试表并插入数据。session1> create table test.t1(id bigint auto_increment primary key,c1 int,c2 int,unique key(c1,c2)); Query OK, 0 rows affected (0.06 sec) session1> insert into test.t1(c1,c2) values(10512475, 1),(10512475, 2),(10512476, 1),(10512476, 2),(10512477, 1),(10512477, 2); Query OK, 6 rows affected (0.04 sec) Records: 6 Duplicates: 0 Warnings: 0 session1> select * from test.t1; +----+----------+------+ id | c1 | c2 | +----+----------+------+ 1 | 10512475 | 1 | 2 | 10512475 | 2 | 3 | 10512476 | 1 | 4 | 10512476 | 2 | 5 | 10512477 | 1 | 6 | 10512477 | 2 | +----+----------+------+ 6 rows in set (0.00 sec) 其次,在会话 2 中针对另外一张表执行FLUSH TABLES FOR EXPORT操作,至于为什么要执行这个操作,后续加锁分析部分会解释。session2> flush tables test.t2 for export; Query OK, 0 rows affected (0.01 sec) 接着,在会话 1 中删除表中数据。session1> delete from test.t1; Query OK, 6 rows affected (0.02 sec) 接着分别创建三个新的会话,执行如下操作:session3> begin; Query OK, 0 rows affected (0.00 sec) session3> insert into test.t1(c1,c2,id) values(10512475,1,100); Query OK, 1 row affected (0.01 sec) session4> begin; Query OK, 0 rows affected (0.00 sec) session4> insert into test.t1(c1,c2,id) values(10512476,1,18158557178); Query OK, 1 row affected (0.00 sec) session5> begin; Query OK, 0 rows affected (0.00 sec) session5> insert into test.t1(c1,c2,id) values(10512477,1,18158557146); Query OK, 1 row affected (0.01 sec) 继续在会话 3 和 会话 4 中插入数据。session3> set session innodb_lock_wait_timeout=5000; Query OK, 0 rows affected (0.00 sec) session3> insert into test.t1(c1,c2) values(10512475, 2); -- 阻塞中... session4> set session innodb_lock_wait_timeout=5000; Query OK, 0 rows affected (0.00 sec) session4> insert into test.t1(c1,c2) values(10512476, 2); -- 阻塞中... 接着在会话 2 中执行UNLOCK TABLES操作释放表锁。session2> unlock tables; Query OK, 0 rows affected (0.00 sec) 在会话 5 中查看锁等待信息。+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+ waiting_trx_id | blocking_trx_id | waiting_lock_mode | waiting_lock_data | blocking_lock_mode | blocking_lock_data | +----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+ 23228 | 23229 | X,GAP,INSERT_INTENTION | 10512477, 1, 18158557146 | S,GAP | 10512477, 1, 18158557146 | 23225 | 23228 | X,GAP,INSERT_INTENTION | 10512476, 1, 18158557178 | S,GAP | 10512476, 1, 18158557178 | +----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+ 2 rows in set (0.01 sec) 可以看到,该结果与重放过程中出现锁等待时的输出完全一致。五、加锁分析接下来,我们重点分析一下:为什么在 RC(Read Committed)事务隔离级别下会产生 GAP 锁?毕竟,在大多数人的印象中,RC 隔离级别下只会存在记录锁,而不会出现间隙锁。事实上,这与INSERT 操作之前执行的 DELETE 操作有直接关系。在前面的例子中,我们在 binlog 中发现,在执行 INSERT 操作之前,存在针对相同记录的 DELETE 操作。在 MySQL 中,DELETE 操作并不会立即物理删除数据,而是将记录标记为“已删除”(delete-marked),等待后台的 purge 线程异步清理。这意味着在逻辑删除之后,这些记录仍然可能暂时保留在索引页中。当随后执行 INSERT 操作时,如果待插入的记录在唯一索引上与某条“已标记删除但尚未清除”的记录键值相同,MySQL 会执行如下加锁行为: 对该索引项加上 S 锁; 同时,对该索引项的间隙(即该记录与下一条记录之间的范围)加上 S,GAP 锁。下面我们通过一个简化的实验来验证这一点。实验验证在前面的重放示例中,我们执行了FLUSH TABLES FOR EXPORT操作。执行这个操作的目的,是为了暂停 purge 线程,从而保留 delete-marked 记录,便于重现这种锁行为。# 会话 1:创建测试表并插入数据 session1> create table test.t1(id bigint auto_increment primary key,c1 int,c2 int,unique key(c1,c2)); Query OK, 0 rows affected (0.07 sec) session1> insert into test.t1(c1,c2) values(10512476, 1),(10512476, 2); Query OK, 2 rows affected (0.04 sec) Records: 2 Duplicates: 0 Warnings: 0 session1> select * from test.t1; +----+----------+------+ id | c1 | c2 | +----+----------+------+ 1 | 10512476 | 1 | 2 | 10512476 | 2 | +----+----------+------+ 2 rows in set (0.00 sec) # 会话 2:暂停 purge 线程 session2> flush tables test.t2 for export; Query OK, 0 rows affected (0.03 sec) # 会话 1:删除数据 session1> delete from test.t1; Query OK, 2 rows affected (0.01 sec) # 会话 3:开启事务并插入数据 session3> begin; Query OK, 0 rows affected (0.00 sec) session3> insert into test.t1(c1,c2,id) values(10512476,1,18158557178); Query OK, 1 row affected (0.01 sec) # 查看锁信息 session3> select object_schema, object_name, index_name, lock_type, lock_mode, lock_status, lock_data from performance_schema.data_locks; +---------------+-------------+------------+-----------+-----------+-------------+--------------------------+ object_schema | object_name | index_name | lock_type | lock_mode | lock_status | lock_data | +---------------+-------------+------------+-----------+-----------+-------------+--------------------------+ test | t1 | NULL | TABLE | IX | GRANTED | NULL | test | t1 | c1 | RECORD | S | GRANTED | 10512476, 1, 1 | test | t1 | c1 | RECORD | S,GAP | GRANTED | 10512476, 2, 2 | test | t1 | c1 | RECORD | S,GAP | GRANTED | 10512476, 1, 18158557178 | +---------------+-------------+------------+-----------+-----------+-------------+--------------------------+ 4 rows in set (0.00 sec) 锁行为解释在插入 (10512476, 1, 18158557178) 这条记录时: 由于 (10512476, 1) 仍存在于索引中(虽然被标记删除),MySQL 会对该记录加上 S 锁; 同时,对(10512476, 1) 的下一条记录 (10512476, 2) 加上 S,GAP 锁,防止该间隙范围内被其他事务插入新记录; 此外,插入的新记录 (10512476, 1, 18158557178) 还会继承下一条记录 (10512476, 2) 的 GAP 锁。其中: 前两种锁的加锁逻辑是在row_ins_scan_sec_index_for_duplicate()中实现的; 锁继承的逻辑是在lock_rec_add_to_queue()中实现的。当执行UNLOCK TABLES后,purge 线程恢复运行,会清理掉之前的 delete-marked 记录,对应的锁也会被释放。但可以看到,新插入记录自身的 GAP 锁仍然保留:session2> unlock tables; Query OK, 0 rows affected (0.05 sec) session3> select object_schema, object_name, index_name, lock_type, lock_mode, lock_status, lock_data from performance_schema.data_locks; +---------------+-------------+------------+-----------+-----------+-------------+--------------------------+ object_schema | object_name | index_name | lock_type | lock_mode | lock_status | lock_data | +---------------+-------------+------------+-----------+-----------+-------------+--------------------------+ test | t1 | NULL | TABLE | IX | GRANTED | NULL | test | t1 | c1 | RECORD | S,GAP | GRANTED | 10512476, 1, 18158557178 | +---------------+-------------+------------+-----------+-----------+-------------+--------------------------+ 2 rows in set (0.01 sec) 六、优化方案针对上面分析的锁等待案例,优化主要可以从应用侧和数据库侧两方面入手。1、应用侧优化应用层面的改进主要集中在索引设计与更新逻辑上: 将原本的唯一索引改为普通二级索引。 将自增主键去掉,直接用原来的唯一索引列作为主键。与普通唯一索引不同,主键在插入时,即使遇到已经删除的记录,也不会额外加 S,GAP 锁。 优化更新逻辑。尽量避免通过DELETE + INSERT的方式更新数据,可以考虑使用UPDATE或者其他业务逻辑调整,以减少对间隙锁的触发。2、数据库侧优化将replica_preserve_commit_order设置为 OFF,允许从库在遇到事务等待环路时,独立提交事务,而无需等待其他事务完成。不过需要注意的是,如果使用的 Group Replication,会要求该参数必须为 ON。下表展示了不同方案下的从库重放性能对比:>>>>参考资料 https://help.aliyun.com/zh/polardb/polardb-for-mysql/resolve-the-unique-key-check-problem-in-mysql http://mysql.taobao.org/monthly/2015/06/02/ https://zhuanlan.zhihu.com/p/28797400192 https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html作者丨陈臣来源丨公众号:MySQL实战(ID:MySQLInAction)dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn返回搜狐,查看更多
2026年03月20日
0 阅读
0 评论
0 点赞
2026-03-20
MySQL 是怎么从巅峰急剧坠落的?
今天看到了一幅图,MySQL历年的Commit数目:从中可以清晰地看出,自2006/2007年达到顶峰以后,Commit数目一路下降,如今的数字只是最高峰的五六分之一!另外一幅图是Stack Overflow的2025年度调查:PostgreSQL排名第一,大幅领先MySQL。这更让我吃惊,因为我记得2022年的时候PostgreSQL还不如MySQL,没想到短短三年差距这么大了。不过,Stack Overflow是用调查的方式,主要统计程序员在开发中使用的数据库,市面上还有很多系统在使用MySQL呢,并没有统计进去。我又赶紧去看了一下MySQL在DB-Engines的排名:还好,MySQL依然排名第二,但是相比一年前,分数大幅下降了(-130.63)。而PostGreSQL从2014年开始,则是一路增长,迅猛上扬。可以说,MySQL的存量市场和使用惯性还在,但精神感召力似乎消失了。这到底是什么原因呢?Amazon RDS for MySQL和MariaDB的前软件开发经理Otto认为:这是因为作为开源项目的管理者,Oracle已经不值得信任了。他甚至说:“2026年别再使用MySQL了,它不是真正的开源软件”,他的主要理由如下:一、开发工作闭门进行公开的bug跟踪系统并不是 Oracle 主要使用的开发工具,少数为MySQL做贡献的人只能眼睁睁看到自己的Pull Request和补丁被标记为已收到,但却得不到任何反馈。这些修改有可能会出现在下一版本的MySQL中,也可能不出现,甚至可能会被重写。在Git的提交者栏中,也只有Oracle员工的名字,真正的作者只能在博客文章中被简单提及一下。这样长此以往,慢慢地就没有人愿意为MySQL做贡献了。换句话说,MySQL虽然通过GPL开源,但它本身的开发方式已经不是一个标准的开源项目了。二、MySQL在技术上衰落了MySQL 8.0.29 把 ALTER TABLE 默认切换到更激进的 in-place 执行方式,但这个实现当时并不成熟,在大量边缘场景下会失败,这直接导致数据库崩溃,数据损坏,影响了许多用户。这个问题直到一年后的 MySQL 8.0.32 才得到彻底解决。2018年MySQL 8.0发布以后,长达6年的时间没有新的重要版本发布,2024年才发布8.1,还没有多少新功能,让很多用户感到失望。更有基准测试表示显示,在写入密集型负载下,MySQL9.5的吞吐量比8.0又下降了15%! 这确实是挺让人崩溃的。在安全方面,MySQL也有不少问题,仅在 2025 年, MySQL 就发布了 123 个关于安全问题的 CVE ,而 MariaDB 只有 8 个 。三、Oracle在引导大家使用收费的/闭源的产品奥托说,MySQL 的软件本身、文档、官网,在各个层面都在引导用户放弃开源版本,转向收费的/闭源的 MySQL 版本。我特意到MySQL的官网(https://www.mysql.com/cn/)看了一下:你别说,首页写得大大的MySQL企业版,MySQL的云数据库HeatWave,AI ,Cluster,就是找不到社区版。我又去看了MySQL各个产品的介绍:这些产品确实让我有点儿晕,哪个是社区版?标准版?经典版? 这是啥? 我搜了一下,发现这两者都是收费的,其实这个页面的所有产品都是收费的。免费的社区版并不在这个MySQL的产品列表中!它都不配有个产品介绍了吗?你要想下载的话,需要到下载页面,拉到最下方,才能看到:2009年,MySQL之父Monty写的一篇文章:Oracle对MySQL的承诺都是空头支票。当时Oracle要收购Sun(MySQL也在Sun旗下),欧盟担心扼杀竞争,几乎否决交易。后来Oracle做出了10项承诺(5年期限),这才使得欧盟放行。Monty说:“Oracle可能会把开发重点放到企业版上,或者添加闭源的扩展,使得社区版的兴趣和活跃度下降。”“他们可能会放弃开源,关闭MySQL的某些部分。”现在看来,Monty的话被应验了。从商业的角度看,5年期限早已过去,Oracle完全可以基于自身利益选择如何发展MySQL这个产品,无可指责。只不过大家也会用脚投票,选择其他真正开源的产品例如MariaDB。MariaDB是MySQL最直接的平替,兼容性非常好。如果是新项目,或者是重要的重构项目,也可以直接上PostGreSQL。按照目前的发展趋势,我感觉两三年内PostgreSQL就在DB-Engines的统计中全面超越MySQL,我个人从零几年就开始用MySQL,大大小小的项目用了很多,对它很有感情,看到他从巅峰坠落,心里真是有点儿伤感。最后顺便提一下,Monty有三个孩子,名称分别是My,Maria,Max。这三个孩子非常幸运,因为他们的父亲把他们的名字永远地“刻”在了三个数据库产品之上: MySQL MaxDB MariaDB本文作者刘欣,著有畅销书《码农翻身》,《半小时漫画计算机》,前IBM架构师,领导过多个企业应用架构设计和开发工作;洞察技术本质,擅长用故事去讲解复杂技术。来源丨公众号:码农翻身(ID:coderising)dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn返回搜狐,查看更多
2026年03月20日
0 阅读
0 评论
0 点赞
2026-03-20
Oracle拒绝放弃MySQL社区版控制权
Oracle正式拒绝了重组MySQL社区版控制权的请求。此前,由数据库公司组成的联盟以及MySQL用户要求Oracle进行这一重组。该联盟的主要成员Percona和VillageSQL本月早些时候与Oracle举行了会议,讨论2月份在线公开信中提出的变更要求。这封公开信得到了至少544名用户的支持,包括数据库专家、开发者和长期贡献者。签署者的主要担忧是Oracle如何管理MySQL代码库的更新。他们认为,这导致数据库失去了大量市场份额,而竞争对手PostgreSQL则从AI驱动工作负载需求激增中获益。公开信还指出,MySQL获得的少量更新并不包含对AI驱动工作负载至关重要的功能,这些功能已经在大多数数据库中成为标准配置,包括Oracle提供的企业版本。签署者建议Oracle将MySQL的开源版本交给独立的非营利基金会管理,由该基金会负责路线图规划、发布治理和贡献者访问权限,同时允许Oracle保留其商业MySQL产品和商标。在公开信发布期间,Oracle MySQL部门的内部变化也未能消除签署者对项目长期管理的担忧。最近的裁员包括Oracle MySQL社区经理Frederic Descamps的离职,他于2月底转投MariaDB基金会。分析师认为,Oracle拒绝放松对数据库的控制是理所当然的。Pareekh Consulting首席分析师Pareekh Jain表示:"将治理权交给基金会意味着放弃路线图决定权,这可能会加速开发与Oracle数据库、Oracle MySQL HeatWave和Oracle商业MySQL企业版竞争的功能。"Greyhound Research首席分析师Sanchit Vir Gogia说,保持对MySQL社区版的管理权能确保开源版本只会以补充其技术组合其余部分的方式发展。尽管Oracle拒绝了联盟放弃控制权的提议,但承诺与MySQL社区继续对话,表示将继续接受关于开发优先级的反馈和围绕社区版的合作。Oracle高管在博客文章中写道:"这种新的开放性和开发速度需要用户和贡献者的深思熟虑的意见和反馈才能成功。这个社区分享的反馈、想法和经验继续塑造我们的方向并加强我们工作的影响力。我们深深致力于在共同发展和改进MySQL时维持开放、透明的对话。"为此,高管们表示Oracle正在提议以AI和云为中心的新路线图规划轨道,以加速推出以开发者为中心的功能,包括一些迄今为止仍是商业版独有的功能。正在探索的新增功能包括使用配置文件引导优化(PGO)创建社区二进制文件、超图优化器,以及旨在简化数据操作语言操作的JSON双重性增强。Oracle还建议可能包含向量函数,但在承诺包含之前正在寻求更多社区反馈。分析师表示,这些新增功能以及更多包容性和透明度的承诺虽然提升了社区版用户的信心,但对MySQL分支提供商来说可能是一把双刃剑。Jain说:"一方面,Oracle更严格的控制可能会增加对真正开源MySQL替代方案的需求,因为寻求企业级功能和MySQL兼容性的用户可能会转向Percona等发行版。另一方面,如果Oracle进一步分化或减慢GPL代码的发布,分支提供商面临不断增长的上游维护负担,迫使他们在回移修复或自己构建核心功能方面投入更多资金。"Jain还表示,如果Oracle未能兑现承诺,MySQL社区版将继续向PostgreSQL失去思维份额——如此之多,以至于Percona等供应商最终可能不得不扩大对PostgreSQL的支持,将自己定位为数据库无关的专家,以对冲MySQL生态系统碎片化的风险。Q&AQ1:Oracle为什么拒绝放弃MySQL社区版的控制权?A:Oracle拒绝是为了保持对MySQL发展方向的控制。放弃治理权意味着失去路线图决定权,可能会加速开发与Oracle数据库、Oracle MySQL HeatWave和Oracle商业MySQL企业版竞争的功能,这与Oracle的商业利益相冲突。Q2:MySQL社区用户对Oracle的管理有哪些不满?A:主要不满包括:Oracle对MySQL代码库更新管理不当,导致数据库失去市场份额给PostgreSQL;MySQL更新缺乏AI驱动工作负载所需的关键功能;这些功能在其他数据库中已成为标准配置,甚至在Oracle自己的企业版中也有提供。Q3:Oracle承诺了哪些改进措施来回应社区关切?A:Oracle承诺继续与MySQL社区对话,提议以AI和云为中心的新路线图规划,加速推出开发者功能。具体包括配置文件引导优化、超图优化器、JSON双重性增强等,还考虑添加向量函数,并承诺保持更开放透明的对话。返回搜狐,查看更多
2026年03月20日
0 阅读
0 评论
0 点赞
1
...
24
25
26
...
40