lolli / dbdoctor
TYPO3数据库医生 - 查找并修复TYPO3数据库不一致性
Requires
- php: ^7.4 || ^8.0
- typo3/cms-core: ^11.5 || ^12.4
Requires (Dev)
- bnf/phpstan-psr-container: ^1.0
- codeception/codeception: ^4.1 || ^5.0.0
- codeception/module-asserts: ^2.0 || ^3.0.0
- codeception/module-cli: ^1.1 || ^2.0.0
- friendsofphp/php-cs-fixer: ^3.52.0
- friendsoftypo3/phpstan-typo3: ^0.9.0
- phpstan/phpstan: ^1.4.6
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^9
- typo3/cms-impexp: ^11.5 || ^12.4
- typo3/cms-redirects: ^11.5 || ^12.4
- typo3/cms-workspaces: ^11.5 || ^12.4
- typo3/testing-framework: ^7.0.2
README
TYPO3 DB医生
使命
本扩展的使命是查找在运行中的TYPO3实例中可能引入的数据库不一致性,并修复它们。
例如,当编辑删除页面树时,有时会发生大多数页面都正确设置为已删除,但遗漏了一些页面,或者某个页面上的一项内容元素未被删除。这会导致数据库中出现孤立的页面或内容元素。
像上述情况一样,出现无效数据库状态的原因可能有多种:一般而言,TYPO3对数据库表没有引用完整性约束,不一致性可能由一个即将结束的PHP进程、丢失的DB连接、核心错误、有缺陷的扩展、损坏的部署等原因触发。通过多次升级多个主要核心版本长期运行的活跃实例,往往会遇到不再完全正确的情况。
这种不一致性可能导致进一步的问题。例如,如果复制了一个有孤立本地化记录的页面,系统往往也会搞乱复制的页面的本地化。然后编辑会出错,TYPO3代理商需要花费大量时间进行调试以找出问题所在。
此扩展提供了一个CLI命令,尝试查找各种此类不一致性,并给管理员提供修复它们的选择。
替代方案
我们不知道其他尝试以类似系统方式实现相同功能的开源扩展。核心的lowlevel
扩展附带了一些尝试清理各种数据库状态的命令,但其代码库相当陈旧,难以维护。
此扩展不是lowlevel
命令的替代品(目前还不是?),它更像是一个孵化器,以查看处理不一致性的特定策略是否在项目中实际可行。它将随着时间的推移而增长。也许它最终会进入核心,或者核心未来将此扩展作为“维护”扩展进行引用。我们将拭目以待。
策略
此命令的策略是逐项检查单个事项,并在进行下一项检查之前修复它们。不正确的记录的更新和删除是通过直接执行低级数据库查询完成的,而不是使用DataHandler。
单个检查是精心制作的,并进行了功能测试,它们的执行顺序很重要。可能会在链中多次运行单个检查。
单个检查更倾向于避免内存消耗和假设状态,以牺牲执行更多查询为代价。查询通常作为预编译语句执行,以便在单个检查中重复使用。单个检查完成后,正确关闭语句,有效地使用PHP垃圾回收。总的来说,此命令即使在大型实例中也非常快速,但它会对数据库造成很大压力。
对前端渲染的影响
当健康检查发现可疑之处时,dbdoctor仅允许一个硬编码的解决方案来处理它。用户不会被要求选择解决方案,要么接受提出的UPDATE或DELETE数据库更改,要么需要手动处理并重新启动(然后重启)。
由于这会给系统增加一个正交的复杂度向量,使得dbdoctor很快变得难以维护,因此无法在dbdoctor中实现按记录的问题/答案功能:单次检查被设计为相互叠加,dbdoctor需要建立一个“正确性链”来执行其任务。
对于特定的“修复”通常有三个选项
- 硬删除违规记录
- 对于支持软删除的表,将记录设置为
deleted=1
- 将记录更新为“更正确”的内容
一般策略是尽量减少从TYPO3 前端渲染角度造成的损害。
例如,当一个特定语言的默认语言记录有两个本地化时,dbdoctor将其检测为无效,并建议将其中一个设置为deleted=1
。在这两个记录中,它将尝试设置一个通常不在前端渲染的已删除记录。
这种一般策略并不总是像上述示例那样简单:由于TYPO3前端渲染非常灵活,实际渲染的记录有时取决于dbdoctor无法了解的特定前端渲染细节。在这些情况下,dbdoctor试图猜测造成损害的最小程度。这不一定总是适合实际情况。唯一解决这个问题的方法是对单个记录更改建议逐个检查。希望交互式选项p
、d
和s
有助于对单个建议更改进行分类。
限制
尽管这个低级工具试图非常小心,并在建议更改之前检查许多细节,但仍有一些限制和假设:例如,软删除感知的TCA表的“删除”列被假设为整数列,而不是文本或varchar或类似类型。只要在ext_tables.sql
文件中没有明确定义,这个列的正确模式通常由核心创建。然而,如果扩展定义了这样的字段并且以某种错误的方式定义,dbdoctor可能会通过建议删除或更新所有行来造成危险。
还有进一步的假设:例如,dbdoctor假设核心为标准表(尤其是pages
、tt_content
和sys_file_reference
)提供的某些TCA设置(特别是ctrl
设置)没有被扩展更改。例如,这些表被假设既支持软删除,又支持工作区,相应字段会被dbdoctor查询。如果扩展修改了这些TCActrl
设置,dbdoctor将会失败。
dbdoctor无法处理的场景还有:例如,假设某些扩展通过在TCA条目中声明['ctrl']['delete'] = 'deleted'
来声明一个表支持软删除,并且你有一些deleted=1
的行。后来,该TCA表被设置为不再支持软删除,通过移除['ctrl']['delete']
声明。核心数据库分析器随后将建议首先将deleted
列重命名为zzz_deleted_deleted
,然后允许删除该列。这样做实际上会将所有之前已删除的记录“激活”,如果你之前没有删除所有受影响的deleted=1
记录。当TCA表变为不再支持工作区,但表中仍有关联工作区的记录时,或者当TCA表不再对“开始时间”/“结束时间”敏感,但表中包含带有时标的记录时,也存在类似的场景。
dbdoctor始终在当前TCA状态下工作。它不知道某些TCA表是否之前已经定义了“软删除感知”,以及是否之后有所改变。当您通过删除“已删除”列、删除“工作空间”扩展、相关工作空间列或与时间相关的字段来将记录推向生产时,这可能会导致无法修复的状态,dbdoctor将无法修复。相反,它可能会找到更多损坏的数据库关系,并提出使情况比之前更糟的建议。此外,dbdoctor永远不会查看可能存在的zzz_deleted
列——从dbdoctor的角度来看,这些列不存在,因为它们依赖于某些“之前”的TCA状态,该状态无法重新构建。上述情况之一创建的状态无法修复,需要手动重建。祝你好运。
总的来说,在dbdoctor之前,TCA和扩展的ext_tables.sql
应该处于良好状态,并且在将更改提交到数据库之前,应始终手动检查健康检查建议。此外,永远不要忘记备份数据库以备不时之需。不要盲目接受dbdoctor的建议!
当前状态
已发布第一版,但我们还没有足够的信心发布1.0.0版本。这个扩展的本质是执行潜在的恶意查询,因此请谨慎使用该系统。然而,我们已经成功地使用这个扩展为一些客户提供服务。
安装
Composer
该扩展目前支持TYPO3 v11和TYPO3 v12。该扩展可以作为非开发依赖项安装(不要在composer require
中添加--dev
):只要它不是通过CLI主动执行,它就不会对生产实例产生影响(除依赖注入定义外)。
$ composer require lolli/dbdoctor
TYPO3扩展存储库
对于非composer项目,该扩展在TER中以扩展密钥dbdoctor
提供,并可以使用扩展管理器进行安装。
准备
CLI命令的本质是在您的实例上执行破坏性数据库操作。因此,应该注意以下几点
-
[!!!] 💣 在 CLI 接口使用前后创建数据库备份。 确保恢复策略确实有效:扩展和用户都可能出错。毕竟,我们正在处理低级数据库操作,事情可能会迅速变得很糟糕。请参阅下面的“进一步提示”部分。
-
[!!!] 确保TYPO3“数据库分析器”满意,不需要新或更改的列或表。早期检查会验证缺失的表和列,但在运行dbdoctor之前进行双重检查仍然是一个好主意。
-
[!!!] 应该没有待处理的内核升级向导。dbdoctor目前不会在执行之前检查所有升级向导是否已执行。
后处理
- [!!!] 当此命令完成后运行引用索引更新器!它很可能将更新某些内容。干净的引用索引在较新的内核版本中变得越来越重要。执行此操作的 CLI 命令:
bin/typo3 referenceindex:update
。
使用
$ bin/typo3 dbdoctor:health
注意dbdoctor是“运行时静态”与TCA:当dbdoctor运行时,TCA在此时不应该改变。当您查看单个更改并决定更改TCA时,请清除所有缓存并中止dbdoctor(在交互模式下按“a”)以重新开始。未能这样做可能会导致dbdoctor将危险提交到数据库,具体取决于您对TCA做了什么。
界面看起来像这样
注意上述图像非常过时,当前版本的界面可能略有不同。我们太懒了,不愿经常更新图像,但它应该给出关于界面外观的坚实基础。
主要命令是一系列的单个检查。它们是依次进行的。受影响的记录详情可以按页和按记录显示,以提供快速概述。界面允许根据检查类型删除或更新受影响的记录。
默认交互模式永远不会自动执行更新,并始终询问用户操作。当按 's'(模拟/显示)时,显示将要执行的查询,当按 'e'(执行)时,实际执行这些查询。
交互模式
当dbdoctor在(默认)交互模式下发现需要修复的内容时,执行会停止并等待用户输入。
- e - 执行建议的更改!
- s - 模拟建议的更改,不执行
- a - 立即中断
- r - 重新加载此检查
- p - 按页显示记录
- d - 显示记录详情
- ? - 帮助
退出值
退出值是位掩码:整数3表示:“需要或已完成更改” AND “用户中断”
- 0 - 无需或已完成更改
- 1 - 需要或已完成更改
- 2 - 用户中断
- 4 - 发生错误
选项
CLI命令可以执行一些选项。默认模式是“交互式”,在每次失败的检查后提示用户输入。
-
帮助概述
$ bin/typo3 dbdoctor:health -h
留给读者自己发现这里做了什么 :-P
-
交互模式:
--mode interactive
或-m interactive
或未给出选项$ bin/typo3 dbdoctor:health -m interactive
默认模式:逐个执行检查,并具有交互式界面以查看受影响的记录详情,显示受影响的记录页,模拟执行查询,重新加载检查,最终执行查询。
-
检查模式:
--mode check
或-m check
$ bin/typo3 dbdoctor:health -m check
运行所有检查但不执行任何数据库更改。如果所有检查都正常,则返回0(零),如果有任何检查发现问题,则返回非零值。用作cron作业以查看在所有问题都修复后,任何检查是否有问题很有用。
-
执行模式:
--mode execute
或-m execute
$ bin/typo3 dbdoctor:health -m execute -f /tmp/dbdoctor-my-instance-`date +%Y-%m-%d-%H-%M-%S`.sql
盲目执行,不进一步询问!这将执行dbdoctor建议的所有更新和删除查询!如果您信任此命令,则这是一个潜在的有破坏性的自动操作,您不应该这样做;-) 您在执行此命令之前创建了数据库备份吗?请注意,
-f
选项在此模式下是强制性的:您必须将执行查询记录到尚不存在的文件中,至少给您提供在dbdoctor破坏您的数据库后调试问题的理论选项。因此,-f
选项中应包含一些日期或类似的内容,以确保其唯一性。 -
将执行查询记录到文件:
--file
或-f
$ bin/typo3 dbdoctor:health -f /tmp/foo.sql
$ bin/typo3 dbdoctor:health -f /tmp/dbdoctor-my-instance-`date +%Y-%m-%d-%H-%M-%S`.sql
将所有更改数据的查询记录到文件。参数必须是一个绝对文件名。请勿将此类文件放入您的实例的公共Web文件夹中。选项
-f
在“交互式”模式下是可用的,在“执行”模式下是必需的。执行更改数据的查询不仅会显示,还会记录到文件。如果命令已在一个使用当前实时数据库镜像的测试系统中执行,则这可能很有用:可以再次审查这些查询,然后使用类似mysql my_database < file.sql
的命令或其他DBMS在实时实例上执行。
当前健康检查
在运行CLI命令时将详细描述单个测试。简要概述
- 页面树完整性检查
- FAL相关sys_file_reference及其相关检查
- 与语言处理相关的检查
- 与工作区相关的检查
- 与内联父子关系相关的检查
其他提示
我们强烈建议管理员在与dbdoctor一起工作时备份数据库。在执行此操作时,必须记住有关SQL转储的一些基本规则。
-
在执行CLI命令前后导出现有的MySQL / MariaDB数据库时,关闭“扩展插入”选项可能很有帮助:默认情况下,
mysqldump
将多个INSERT语句合并为一个调用以提高效率和速度。这样导出和导入都更快,且占用的磁盘空间更少。然而,当寻找单个数据库更改时,关闭此选项并且每行插入一行会更方便。当搜索最终出错的内容时,工具如
diff
就更容易理解了。以下是一些示例shell命令$ mysqldump --skip-extended-insert myDatabase > /tmp/myDatabase-`date +%Y-%m-%d-%H-%M-%S`-dbdoctor-before.sql $ bin/typo3 dbdoctor:health $ mysqldump --skip-extended-insert myDatabase > /tmp/myDatabase-`date +%Y-%m-%d-%H-%M-%S`-dbdoctor-after.sql
尽管如此,如果你知道自己在做什么,你总是可以偏离常规。我通常的做法是创建两个备份:一个带有
--skip-extended-insert
,一个不带。当从没有“每行一个行”的文件加载时,灾难恢复会更快,但为了调试,比较基于跳过扩展插入的备份会更简单。 -
当导出数据库时,将此类备份放入可由Web服务器或第三方服务器用户访问的公开目录是一个至关重要的安全措施。违反这条基本规则是野外数据泄露的常见原因!没有任何借口会出错。将SQL文件放置在可以旋转到备份的位置也是一个好主意,以便稍后出现问题时可以调试。为了遵守GDPR规则,这些文件应该在某个时候被删除!
-
当导出数据库时,通常最好将.sql文件gzip压缩:这通常可以将文件大小减少到原来的八分之一。让我们节省一些宝贵的服务器磁盘和备份空间!在导出时也可以直接将数据“管道”到gzip。这样做,或者记得在退出系统之前压缩文件。
常见问题解答(FAQ)
-
该功能将在后端GUI中提供吗?
不会。CLI是处理这类事情的唯一合理方式。
-
是否会添加对TYPO3 v10或其他旧核心版本的支持?
不会。TYPO3 v11进行了一些数据库更改,并且没有计划实现v10向后兼容层。
标记和发布
packagist.org通过github的日常钩子启用。当使用tailor标记版本时,通过“publish.yml”github工作流创建TER发布。标记提交的提交信息用作TER上传评论。
示例
Build/Scripts/runTests.sh -s clean
Build/Scripts/runTests.sh -s composerUpdate
composer req --dev typo3/tailor
.Build/bin/tailor set-version 0.3.2
composer rem --dev typo3/tailor
git commit -am "[RELEASE] 0.3.2 Added some basic inline foreign field related checks"
git tag 0.3.2
git push
git push --tags