GStreamer 的调试工具
目标
有时一些事情没有按照预期的运行,但从总线(bus)获得的错误消息也没有提供足够的信息。幸运地是,GStreamer 带有大量的调试信息,它们通常可以对哪里出了问题给出一些提示。这里将介绍:
- 如何从 GStreamer 获得更多调试信息。
- 如何把自己的调试信息打印到 GStreamer 日志里。
- 如何获得管线图。
打印调试信息
调试日志
GStreamer 和它的插件充满了调试追踪,即,放置在有趣的特定代码片段处,信息片段将被打印到终端,伴随着时间戳,进程,种类,源码文件,函数,和元素信息。
调试输出由 GST_DEBUG
环境变量控制。这里有一个 GST_DEBUG=2
的例子:
0:00:00.868050000 1592 09F62420 WARN filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"
如你所见,这是相当多的信息。事实上,GStreamer 调试日志是如此的详细,当完全启用时,它会使应用程序失去响应(由于控制台滚动)或者填满数兆字节的文本文件(当重定向到一个文件时)。因此,日志是分类的,我们很少需要一次启用所有类别。
第一类是调试级别,它是一个指定了想要的输出的数字:
| # | Name | Description |
|---|---------|----------------------------------------------------------------|
| 0 | none | No debug information is output. |
| 1 | ERROR | Logs all fatal errors. These are errors that do not allow the |
| | | core or elements to perform the requested action. The |
| | | application can still recover if programmed to handle the |
| | | conditions that triggered the error. |
| 2 | WARNING | Logs all warnings. Typically these are non-fatal, but |
| | | user-visible problems are expected to happen. |
| 3 | FIXME | Logs all "fixme" messages. Those typically that a codepath that|
| | | is known to be incomplete has been triggered. It may work in |
| | | most cases, but may cause problems in specific instances. |
| 4 | INFO | Logs all informational messages. These are typically used for |
| | | events in the system that only happen once, or are important |
| | | and rare enough to be logged at this level. |
| 5 | DEBUG | Logs all debug messages. These are general debug messages for |
| | | events that happen only a limited number of times during an |
| | | object's lifetime; these include setup, teardown, change of |
| | | parameters, etc. |
| 6 | LOG | Logs all log messages. These are messages for events that |
| | | happen repeatedly during an object's lifetime; these include |
| | | streaming and steady-state conditions. This is used for log |
| | | messages that happen on every buffer in an element for example.|
| 7 | TRACE | Logs all trace messages. Those are message that happen very |
| | | very often. This is for example is each time the reference |
| | | count of a GstMiniObject, such as a GstBuffer or GstEvent, is |
| | | modified. |
| 9 | MEMDUMP | Logs all memory dump messages. This is the heaviest logging and|
| | | may include dumping the content of blocks of memory. |
+------------------------------------------------------------------------------+
为了开启调试输出,可以把 GST_DEBUG
环境变量设置为需要的调试级别。所有设置的级别之下级别的日志也将被展示(比如,如果设置 GST_DEBUG=2
,你将同时获得 ERROR
和 WARNING
的消息)。
此外,每个插件或 GStreamer 的部分定义了它们自己的类别,因此,你可以给每一个单独的类别指定一个调试等级。比如,GST_DEBUG=2,audiotestsrc:6
,将为 audiotestsrc
元素使用调试等级 6,为其它部分使用调试等级 2。
然后,GST_DEBUG
环境变量,是一个逗号分隔的 category:level 对的列表,在开头有一个可选的 level
,表示所有类别的默认调试等级。
使用 '*'
通配符也是可以的。比如,GST_DEBUG=2,audio*:6
将为所有以 audio
开头的类别使用调试等级 5。GST_DEBUG=*:2
等价于 GST_DEBUG=2
。
使用 gst-launch-1.0 --gst-debug-help
来获取所有已经注册的类别。考虑到每个插件注册它自己的类别,因此,当安装或移除插件时,获得的这个列表可能会变化。
当在 GStreamer 总线上抛出的错误信息无助于帮你缩小问题的范围时使用 GST_DEBUG
。将输出日志重定向到一个文件,稍后检查它,并从中搜索特定的消息是非常常见的一种实践。
GStreamer 允许定制调试信息处理程序,但当使用默认的处理程序时,调试输出中每一行的内容看起来像这样:
0:00:00.868050000 1592 09F62420 WARN filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"
这里是信息被格式化的方式:
| Example | Explained |
|------------------|-----------------------------------------------------------|
|0:00:00.868050000 | Time stamp in HH:MM:SS.sssssssss format since the start of|
| | the program. |
|1592 | Process ID from which the message was issued. Useful when |
| | your problem involves multiple processes. |
|09F62420 | Thread ID from which the message was issued. Useful when |
| | your problem involves multiple threads. |
|WARN | Debug level of the message. |
|filesrc | Debug Category of the message. |
|gstfilesrc.c:1044 | Source file and line in the GStreamer source code where |
| | this message was issued. |
|gst_file_src_start| Function that issued the message. |
|<filesrc0> | Name of the object that issued the message. It can be an |
| | element, a pad, or something else. Useful when you have |
| | multiple elements of the same kind and need to distinguish|
| | among them. Naming your elements with the name property |
| | makes this debug output more readable but GStreamer |
| | assigns each new element a unique name by default. |
| error: No such | |
| file .... | The actual message. |
+------------------------------------------------------------------------------+
添加你自己的调试信息
在你自己的与 GStreamer 交互的代码部分,使用 GStreamer 的调试工具也很有趣。用这种方式,你将所有调试输出都保存在同一个文件中,并且保留了不同消息之间的临时关系。
要做到这一点,可以使用 GST_ERROR()
,GST_WARNING()
,GST_INFO()
,GST_LOG()
和 GST_DEBUG()
宏。它们接受与 printf
相同的参数,且它们使用 default
类别(在输出日志中 default
将被显示为调试类别)。
要想修改类别为其它更有意义的东西,则在你的代码的顶部添加如下两行:
GST_DEBUG_CATEGORY_STATIC (my_category);
#define GST_CAT_DEFAULT my_category
在用 gst_init()
初始化 GStreamer 之后的一行添加如下的行:
GST_DEBUG_CATEGORY_INIT (my_category, "my category", 0, "This is my very own");
这注册一个新的类别(也就是说,在应用程序运行期间:它不存储在任何文件中),并把它设置为你的代码的默认类别。参考文档来了解更多关于 GST_DEBUG_CATEGORY_INIT()
的内容。
获取管线的图
对于那种你的管线已经开始变得太大,且你已经丢失了对于哪个节点与另一个什么节点连接的追踪的情况,GStreamer 具有输出图文件的能力。它们是 .dot
文件,它们可以通过自由程序如 GraphViz 来读,它描述了你的管线的拓扑,以及每个连接上协商的能力。
当使用如 playbin
或 uridecodebin
这样的在它们内部初始化多个元素的一体式元素的时候,这也非常方便。使用 .dot
文件学习它们已经在内部创建了什么样的管线(并在此过程中学习一些 GStreamer 相关的东西)。
为了获得 .dot
文件,简单地设置 GST_DEBUG_DUMP_DOT_DIR
环境变量指向你想要文件被放置的目录即可。gst-launch-1.0
将在每次状态改变时创建一个 .dot
文件,因此你可以看到功能协商的进化。重置该变量以禁用这个功能。在你的应用程序内,你可以使用 GST_DEBUG_BIN_TO_DOT_FILE()
和 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()
宏来在你方便的时候生成 .dot
文件。
这里有一个 playbin
生成的管线的种类的例子。它非常复杂,因为 playbin
可以处理非常多种情况:你手动搭建的管线通常不需要这么长。如果你手动创建的管线开始变得非常大,则可以考虑使用 playbin
。
要下载全尺寸图片,使用本页顶部的附件链接(即回形针图标)。
GST_DEBUG_DUMP_DOT_DIR
环境变量在 gstreamer/tools/gst-launch.c
源文件中有获取,在 gstreamer/tools/gst-launch.c
源文件中可以看到如下这段代码:
#ifdef G_OS_UNIX
static gboolean
hup_handler (gpointer user_data)
{GstElement *pipeline = (GstElement *) user_data;if (g_getenv ("GST_DEBUG_DUMP_DOT_DIR") != NULL) {PRINT ("SIGHUP: dumping dot file snapshot ...\n");} else {PRINT ("SIGHUP: not dumping dot file snapshot, GST_DEBUG_DUMP_DOT_DIR ""environment variable not set.\n");}/* dump graph on hup */GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),GST_DEBUG_GRAPH_SHOW_ALL, "gst-launch.snapshot");return G_SOURCE_CONTINUE;
}
#endif
可以看到,这个环境变量接口只有在类 Unix 系统中才工作。但这里也仅仅是获取了一下,并检查了获取的值而已。这里我们没有看到任何有关打开文件或者写文件之类的操作。将媒体流处理的管线写入文件的操作也是通过宏 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()
完成的。
除了 gstreamer/tools/gst-launch.c
源文件外,在源文件 gstreamer/gst/gst.c
中也获取了这个环境变量:
static gboolean
init_pre (GOptionContext * context, GOptionGroup * group, gpointer data,GError ** error)
{gchar *libdir;if (gst_initialized) {GST_DEBUG ("already initialized");return TRUE;}find_executable_path ();_priv_gst_start_time = gst_util_get_timestamp ();#ifndef GST_DISABLE_GST_DEBUG_priv_gst_debug_init ();priv_gst_dump_dot_dir = g_getenv ("GST_DEBUG_DUMP_DOT_DIR");
#endif
init_pre()
这个函数在 GStreamer 初始化时,将获取的环境变量 GST_DEBUG_DUMP_DOT_DIR
的值,即要写入管线图文件的目录保存在全局变量 priv_gst_dump_dot_dir
中。
在源文件 gstreamer/gst/gstdebugutils.c
的用于将管线的图写入文件的 gst_debug_bin_to_dot_file()
函数中,读取全局变量 priv_gst_dump_dot_dir
的值,来获得要保存文件的目录,并将管线图写入文件:
void
gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details,const gchar * file_name)
{gchar *full_file_name = NULL;FILE *out;g_return_if_fail (GST_IS_BIN (bin));if (G_LIKELY (priv_gst_dump_dot_dir == NULL))return;if (!file_name) {file_name = g_get_application_name ();if (!file_name)file_name = "unnamed";}full_file_name = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.dot",priv_gst_dump_dot_dir, file_name);if ((out = fopen (full_file_name, "wb"))) {gchar *buf;buf = gst_debug_bin_to_dot_data (bin, details);fputs (buf, out);g_free (buf);fclose (out);GST_INFO ("wrote bin graph to : '%s'", full_file_name);} else {GST_WARNING ("Failed to open file '%s' for writing: %s", full_file_name,g_strerror (errno));}g_free (full_file_name);
}
由此可见,环境变量 GST_DEBUG_DUMP_DOT_DIR
是 GStreamer 框架的配置接口,而不仅仅是 gst-launch 这个工具的配置接口。
GST_DEBUG_BIN_TO_DOT_FILE()
和 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()
宏的原型如下:
#define GST_DEBUG_BIN_TO_DOT_FILE(bin, details, file_name)
#define GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin, details, file_name)
其中 bin
为应该分析的顶级的管线, details
为图中显示的详细信息,如 GST_DEBUG_GRAPH_SHOW_ALL
或一个或多个 GstDebugGraphDetails
标记,file_name
为输出的基名,也就是输出文件的文件名的模式,而不是精确的文件名。
这两个宏的定义为:
#define GST_DEBUG_BIN_TO_DOT_FILE(bin, details, file_name) gst_debug_bin_to_dot_file (bin, details, file_name)
#define GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin, details, file_name) gst_debug_bin_to_dot_file_with_ts (bin, details, file_name)
它们是对两个 C 函数的调用。前面我们已经看到了函数 gst_debug_bin_to_dot_file()
的定义,这里再来看下 gst_debug_bin_to_dot_file_with_ts()
函数的定义:
void
gst_debug_bin_to_dot_file_with_ts (GstBin * bin,GstDebugGraphDetails details, const gchar * file_name)
{gchar *ts_file_name = NULL;GstClockTime elapsed;g_return_if_fail (GST_IS_BIN (bin));if (!file_name) {file_name = g_get_application_name ();if (!file_name)file_name = "unnamed";}/* add timestamp */elapsed = GST_CLOCK_DIFF (_priv_gst_start_time, gst_util_get_timestamp ());/* we don't use GST_TIME_FORMAT as such filenames would fail on some* filesystems like fat */ts_file_name =g_strdup_printf ("%u.%02u.%02u.%09u-%s", GST_TIME_ARGS (elapsed),file_name);gst_debug_bin_to_dot_file (bin, details, ts_file_name);g_free (ts_file_name);
}
gst_debug_bin_to_dot_file_with_ts()
函数是对函数 gst_debug_bin_to_dot_file()
的封装。
由此我们看到,要让 GStreamer 将管线图写入文件要完成的两个步骤:
- 设置环境变量
GST_DEBUG_DUMP_DOT_DIR
指向一个用于保存管线图文件的目录; - 通过
GST_DEBUG_BIN_TO_DOT_FILE()
和GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()
宏写一个文件。
这两个步骤缺一不可。只是对于 gst-launch 工具来说,第 2 步由工具执行。
如对于 GStreamer 的示例程序 helloworld,我们为它加上将管线图写入文件的代码:
loop = g_main_loop_new (NULL, FALSE);bus = gst_element_get_bus (playbin);bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);gst_object_unref (bus);GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (playbin),GST_DEBUG_GRAPH_SHOW_ALL, "gst-helloworld");/* start play back and listed to events */gst_element_set_state (playbin, GST_STATE_PLAYING);GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (playbin),GST_DEBUG_GRAPH_SHOW_ALL, "gst-helloworld");g_main_loop_run (loop);
同时设置环境变量:
~/Data/opensource/gstreamer$ export GST_DEBUG_DUMP_DOT_DIR=/home/hanpfei/Data/opensource/gstreamer/
当我们将 helloworld 重新编译并运行起来时,
~/Data/opensource/gstreamer$ build/tests/examples/helloworld/helloworld ~/music.mp3
将在 /home/hanpfei/Data/opensource/gstreamer/
目录下生成两个 .dot
文件,类似于下面这样:
0.00.00.205294209-gst-helloworld.dot
0.00.00.206333290-gst-helloworld.dot
其中的 0.00.00.206333290-gst-helloworld.dot
文件的内容如下:
digraph pipeline {rankdir=LR;fontname="sans";fontsize="10";labelloc=t;nodesep=.1;ranksep=.2;label="<GstPlayBin>\nplaybin0\n[-] -> [>]\ncurrent-uri=\"file:///home/hanpfei/music.mp3\"\nsource=(GstFileSrc) source";node [style="filled,rounded", shape=box, fontsize="9", fontname="sans", margin="0.0,0.0"];edge [labelfontsize="6", fontsize="9", fontname="monospace"];legend [pos="0,0!",margin="0.05,0.05",style="filled",label="Legend\lElement-States: [~] void-pending, [0] null, [-] ready, [=] paused, [>] playing\lPad-Activation: [-] none, [>] push, [<] pull\lPad-Flags: [b]locked, [f]lushing, [b]locking, [E]OS; upper-case is set\lPad-Task: [T] has started task, [t] has paused task\l",];subgraph cluster_uridecodebin0_0x557be77e20d0 {fontname="Bitstream Vera Sans";fontsize="8";style="filled,rounded";color=black;label="GstURIDecodeBin\nuridecodebin0\n[-] -> [=]\nuri=\"file:///home/hanpfei/music.mp3\"\nsource=(GstFileSrc) source\ncaps=video/x-raw(ANY); audio/x-raw(ANY); text/x-raw(ANY); subpicture/x-dvd; subpictur…";fillcolor="#ffffff";subgraph cluster_decodebin0_0x557be77e80f0 {fontname="Bitstream Vera Sans";fontsize="8";style="filled,rounded";color=black;label="GstDecodeBin\ndecodebin0\n[-] -> [=]\ncaps=video/x-raw(ANY); audio/x-raw(ANY); text/x-raw(ANY); subpicture/x-dvd; subpictur…";subgraph cluster_decodebin0_0x557be77e80f0_sink {label="";style="invis";_proxypad0_0x557be77e2360 [color=black, fillcolor="#ddddff", label="proxypad0\n[<][bfb]", height="0.2", style="filled,solid"];decodebin0_0x557be77e80f0_sink_0x557be77ee060 -> _proxypad0_0x557be77e2360 [style=dashed, minlen=0]decodebin0_0x557be77e80f0_sink_0x557be77ee060 [color=black, fillcolor="#ddddff", label="sink\n[<][bfb]", height="0.2", style="filled,solid"];}fillcolor="#ffffff";subgraph cluster_typefind_0x557be77ea010 {fontname="Bitstream Vera Sans";fontsize="8";style="filled,rounded";color=black;label="GstTypeFindElement\ntypefind\n[=]\ncaps=application/x-id3";subgraph cluster_typefind_0x557be77ea010_sink {label="";style="invis";typefind_0x557be77ea010_sink_0x557be77e6440 [color=black, fillcolor="#aaaaff", label="sink\n[<][bfb][T]", height="0.2", style="filled,solid"];}subgraph cluster_typefind_0x557be77ea010_src {label="";style="invis";typefind_0x557be77ea010_src_0x557be77e6690 [color=black, fillcolor="#ffaaaa", label="src\n[>][bfb]", height="0.2", style="filled,solid"];}typefind_0x557be77ea010_sink_0x557be77e6440 -> typefind_0x557be77ea010_src_0x557be77e6690 [style="invis"];fillcolor="#aaffaa";}_proxypad0_0x557be77e2360 -> typefind_0x557be77ea010_sink_0x557be77e6440 [label="ANY"]}subgraph cluster_source_0x557be77e4220 {fontname="Bitstream Vera Sans";fontsize="8";style="filled,rounded";color=black;label="GstFileSrc\nsource\n[=]\nlocation=\"/home/hanpfei/music.mp3\"";subgraph cluster_source_0x557be77e4220_src {label="";style="invis";source_0x557be77e4220_src_0x557be77e61f0 [color=black, fillcolor="#ffaaaa", label="src\n[<][bfb]", height="0.2", style="filled,solid"];}fillcolor="#ffaaaa";}source_0x557be77e4220_src_0x557be77e61f0 -> decodebin0_0x557be77e80f0_sink_0x557be77ee060 [label="ANY"]}subgraph cluster_playsink_0x557be6b00290 {fontname="Bitstream Vera Sans";fontsize="8";style="filled,rounded";color=black;label="GstPlaySink\nplaysink\n[-] -> [=]\nflags=video+audio+text+soft-volume+deinterlace+soft-colorbalance\nsend-event-mode=first";fillcolor="#ffffff";subgraph cluster_streamsynchronizer0_0x557be6b02020 {fontname="Bitstream Vera Sans";fontsize="8";style="filled,rounded";color=black;label="GstStreamSynchronizer\nstreamsynchronizer0\n[=]";fillcolor="#ffffff";}}}
.dot
文件是图的一种文本形式的描述。
要看到图,还需要通过 GraphViz 将 .dot
文件转为 png 等格式。
安装 graphviz:
~/Data/opensource/gstreamer$ sudo apt-get install graphviz
通过 graphviz
包中的 dot
工具将 .dot
文件转为 PNG 图:
~/Data/opensource/gstreamer$ dot -Tpng -o test1.png 0.00.00.205294209-gst-helloworld.dot
~/Data/opensource/gstreamer$ dot -Tpng -o test2.png 0.00.00.206333290-gst-helloworld.dot
生成的图如下:
GStreamer 的媒体处理管线在不同时刻其状态不太一样,使得获得的图也不太一样。
结论
这里展示了:
- 如何使用
GST_DEBUG
环境变量从 GStreamer 获取更多调试信息 - 如何通过
GST_ERROR()
及其相关的宏将你自己的调试信息打印到 GStreamer 日志中。 - 如何通过
GST_DEBUG_DUMP_DOT_DIR
环境变量获得管线图。
参考文档:
Basic tutorial 11: Debugging tools
Gstreamer 管道可视化
GStreamer 的调试工具相关推荐
- GStreamer基础教程10——GStreamer工具
目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...
- Gstreamer基础教程10: Gstreamer 工具
文章目录 1. Goal 2. 介绍 3. gst-lanuch-1.0 3.1 Elements 3.2 Properties 3.3 Named elements 3.4 Pads 3.5 Cap ...
- 多平台Gstreamer Multiplatform
多平台Gstreamer Multiplatform GStreamer可在所有主要操作系统上运行,例如Linux,Android,Windows,Max OS X,iOS,以及大多数BSD,商业Un ...
- GStreamer 1.18.4稳定的错误修复版本
GStreamer 1.18.4稳定的错误修复版本 GStreamer团队宣布最喜欢的跨平台多媒体框架的稳定的1.18版本系列中的另一个错误修复版本! 此版本仅包含错误修复和重要的安全修复程序,并且从 ...
- 什么是GStreamer?
什么是GStreamer? GStreamer是用于创建流媒体应用程序的框架.基本设计来自俄勒冈大学研究生院的视频管道以及DirectShow的一些想法. GStreamer的开发框架使编写任何类型的 ...
- gst-crypto GStreamer插件
gst-crypto GStreamer插件 内容 • 1. gst-crypto概述 o 1.1gst-crypto GStreamer插件功能 o 1.2用例范例 • 2. GStreamer插件 ...
- GStreamer跨平台多媒体框架
GStreamer跨平台多媒体框架 Gstreamer基本概念 GStreamer是用于构造媒体处理组件图的库.它支持的应用程序范围从简单的Ogg / Vorbis回放,音频/视频流到复杂的音频(混合 ...
- GStreamer 1.0 series序列示例
GStreamer 1.0 series序列示例 OpenEmbedded layer for GStreamer 1.0 这layer层为GStreamer 1.0框架提供了非官方的支持,用于Ope ...
- 使用 Strace 调试工具
strace是Linux环境下的一款程序调试工具,用来监察一个应用程序所使用的系统呼叫及它所接收的系统信息. strace是一个有用的小工具,它可以通过跟踪系统调用来让你知道一个程序在后台所做的事情. ...
最新文章
- android datepicker使用方法,android DatePicker
- 5GS 协议栈 — Overview
- PaddleOCR——C++服务端部署Visual Studio 2019 环境下CMake 编译错误【无法打开输入文件paddle_fluid.lib】解决方案
- C#开发和使用中的23个技巧
- ASP.NET 2.0:如何让DropDownList同时拥有数据来源项目与自订项目 (转自章立民CnBlogs)...
- 服务器操作系统的安装步骤,服务器操作系统的安装步骤
- 2017 ACM Jordanian Collegiate Programming Contest
- java编程基础码_1.java编程基础
- LinkedIn会成为下一个诺基亚吗?
- android person类_骚操作:不重启 JVM,如何替换掉已经加载的类?
- 软件测试的支付流程图,软件测试流程图案例.doc
- VR全景制作方法教程完整版
- 【直流潮流】基于直流潮流的电力系统停电分布及自组织临界性分析
- css3橙色球形_CSS3橙色的星球绕轨道公转动画_html/css_WEB-ITnose -
- 计算机中文速录技能,亚伟中文速录机训教程(6.0版).doc
- 深入浅出学算法007-统计求和
- 浅析 Google 的云计算平台
- MinGW和GCC所有版本下载地址
- elasticSearch的安装与使用
- 【毕业N年系列】 毕业第一年
热门文章
- ios键盘横屏_iOS横竖屏旋转及其基本适配方法 转
- 解决MySQL删除外键时报错Error Code: 1091. Can‘t DROP ‘XXX‘; check that column/key exists
- JDBC、DriverManage、JNDI、数据源(DataSource)、连接池的区别
- int型数据占用的内存空间及ASCII码表
- nginx配置文件详细解读
- 写一个 JavaScript 框架:比 setTimeout 更棒的定时执行
- ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态
- 233. Number of Digit One
- TransactionScope 分布式事务
- 有向图的邻接表表示法