前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[ibd2sql] MYSQL JSON数据类型在磁盘上的存储结构并使用py3去解析 (修改时间:2024.01.05)

[ibd2sql] MYSQL JSON数据类型在磁盘上的存储结构并使用py3去解析 (修改时间:2024.01.05)

原创
作者头像
大大刺猬
修改2024-01-05 08:52:23
2110
修改2024-01-05 08:52:23
举报
文章被收录于专栏:大大刺猬

mysql支持json格式的数据类型, json格式的数据binary为mysql层实现的, 所以字节序是小端. 解析的时候要注意下

innodb存储它的时候是当作big类型来处理的, 所以innodb只要读出该二进制数据即可, 剩下的就交给Mysql我们来处理.

JSON磁盘上的格式

mysql官方还是介绍得很详细的. 文件: sql/json_binary.h

说明: 下面出现的2/4字节 表示: 如果是小small object/array 就使用2字节, large object/array就使用4字节

第一部分, 使用字节表示 数据类型

代码语言:c
复制
  type ::=
      0x00 |       // small JSON object
      0x01 |       // large JSON object
      0x02 |       // small JSON array
      0x03 |       // large JSON array
      0x04 |       // literal (true/false/null) 1:true  2:false  0:null
      0x05 |       // int16
      0x06 |       // uint16
      0x07 |       // int32
      0x08 |       // uint32
      0x09 |       // int64
      0x0a |       // uint64
      0x0b |       // double
      0x0c |       // utf8mb4 string
      0x0f         // custom data (any MySQL data type)

简化下就是这样的

代码语言:c
复制
  value ::=
      object  |
      array   |
      literal |
      number  |
      string  |
      custom-data

第二部分 使用2/4字节表示 元素数量

第三部分 使用2/4字节 表示这个json obj/arr大小(不含json类型所使用的1字节)

第三部分 是key-entry 和 vaue-entry (array不需要key-entry)

第四部分 是value 可以是上面的那些数据类型, 也就是还可以是json obj/arr 如果是json obj/arr 就继续第二步到第四部 (递归), 如果是utf8mb4字符串的话, 会有一字节记录数据大小(毕竟value_entry只记录了偏移量....)

画图的话就是这样

代码语言:py
复制
                                                               - -----------------
                                                              | JSON OBJECT/ARRAY |
                                                               - -----------------
                                                                      |
 -------------------------------------------------------------------------
| TYPE | ELEMENT_COUNT | KEY-ENTRY(if object) | VALUE-ENTRY | KEY | VALUE |
 -------------------------------------------------------------------------
                               |                    |          |
                               |                    |     --------------
                   --------------------------       |    | UTF8MB4 DATA |
                  | KEY-OFFSET |  KEY-LENGTH |      |     --------------
                   --------------------------       |
                                                    |
                                         --------------------------------
                                         | TYPE | OFFSET/VALUE(if small) |
                                         --------------------------------



OBJECT VALUE = KEY_ENTRY + VALUE_ENTRY + KEY + VALUE  #KEY肯定是字符串, 所以不需要记录数据类型
ARRAY  VALUE = VALUE_ENTRY + VALUE #不需要KEY

KEY_ENTRY   = KEY_OFFSET(2/4bytes) + KEY_LNGTH(2 bytes)
VALUE_ENTRY = TYPE(1byte) + OFFSET(2/4 bytes)/VALUE  (如果类型是int,literal之类的,就直接是值了, 否则就走OFFSET)

困难点

解析为dict对象很简单, 但有true,false,null的时候, 拼接为sql就有点难度了....

正当我准备人工拼接字符串的时候, 我想起了还有json包, 可以直接使用json.dumps 来做(........)

使用脚本解析

从ibd文件解析出json对象的过程这里就省略了. (感兴趣的可以看下之前讲解的ibd文件解析)

就假设你已经解析出来如下json原始数据了

代码语言:python
代码运行次数:0
复制
(venv) 14:34:40 [root@ddcw21 ibd2sql_v1.0]#python test.py /data/mysql_3314/mysqldata/ibd2sql/t20240102_js.ibd 
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (1, 'aa', '\x00\x01\x00\r\x00\x0b\x00\x02\x00\x05{\x00t1');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (2, 'aa', '\x00\x01\x00,\x00\x0b\x00\x02\x00\x0c\r\x00t1\x1eAAAAAAAAAAAAAAAAACBBBBBBBBBBBB');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (3, 'aa', '\x00\x02\x00)\x00\x12\x00\x02\x00\x14\x00\x02\x00\x00\x16\x00\x0c&\x00a1a2\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00b1\x02b1\x02a6');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (4, 'bb', '\x00\x01\x00\x11\x00\x0b\x00\x02\x00\x0c\r\x00t1\x03ABC');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (5, 'bb', '\x00\x01\x00\r\x00\x0b\x00\x02\x00\x04\x02\x00t1');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (6, 'bb', '\x02\x02\x00*\x00\x00\n\x00\x00\x1a\x00\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00AA\x02AA\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00BB\x02BB');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (7, 'bb', "\x02\x02\x007\x00\x00\n\x00\x00'\x00\x01\x00\x1d\x00\x0b\x00\x02\x00\x00\r\x00AA\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00CC\x02CC\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00BB\x02BB");
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (8, 'bb', '\x02\x02\x00D\x00\x00\n\x00\x004\x00\x01\x00*\x00\x0b\x00\x02\x00\x00\r\x00AA\x01\x00\x1d\x00\x0b\x00\x02\x00\x00\r\x00CC\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00DD\x02DD\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00BB\x02BB');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (9, 'bb', '\x02\x03\x00T\x00\x00\r\x00\x007\x00\x00G\x00\x01\x00*\x00\x0b\x00\x02\x00\x00\r\x00AA\x01\x00\x1d\x00\x0b\x00\x02\x00\x00\r\x00CC\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00DD\x02DD\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00BB\x02BB\x01\x00\r\x00\x0b\x00\x02\x00\x05\x02\x00FF');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (10, 'bb', '\x02\x03\x00T\x00\x00\r\x00\x007\x00\x00G\x00\x01\x00*\x00\x0b\x00\x02\x00\x00\r\x0013\x01\x00\x1d\x00\x0b\x00\x02\x00\x00\r\x00CC\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00DD\x02DD\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00BB\x02BB\x01\x00\r\x00\x0b\x00\x02\x00\x05\x02\x00FF');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (11, 'bb', '\x00\x03\x00\x1f\x00\x19\x00\x02\x00\x1b\x00\x02\x00\x1d\x00\x02\x00\x04\x01\x00\x04\x02\x00\x04\x00\x00aabbcc');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (12, 'cc', '\x00\x03\x00\x1f\x00\x19\x00\x02\x00\x1b\x00\x02\x00\x1d\x00\x02\x00\x05\x01\x00\x05\x02\x00\x05\x00\x00aabbcc');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (13, 'cc', '\x02\x01\x00\x14\x00\x00\x07\x00\x01\x00\r\x00\x0b\x00\x02\x00\x05\x01\x00aa');

好我们现在使用 jsonob去解析该二进制对象为dict/list

代码语言:python
代码运行次数:0
复制
>>> from ibd2sql.mysql_json import jsonob
>>> data = b'\x02\x03\x00T\x00\x00\r\x00\x007\x00\x00G\x00\x01\x00*\x00\x0b\x00\x02\x00\x00\r\x0013\x01\x00\x1d\x00\x0b\x00\x02\x00\x00\r\x00CC\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00DD\x02DD\x01\x00\x10\x00\x0b\x00\x02\x00\x0c\r\x00BB\x02BB\x01\x00\r\x00\x0b\x00\x02\x00\x05\x02\x00FF'
>>> 
>>> jsonob(data[1:],int.from_bytes(data[:1],'little'))
<ibd2sql.mysql_json.jsonob object at 0x7f3ef7509dd8>
>>> jsonob(data[1:],int.from_bytes(data[:1],'little')).init()
[{'13': {'CC': {'DD': 'DD'}}}, {'BB': 'BB'}, {'FF': 2}]
>>> 

注意该class 第一个参数为数据, 第二个参数为类型(int)

然后把该class 引入到我们的ibd2sql脚本中. 就能得到如下的sql了

代码语言:shell
复制
(venv) 14:55:09 [root@ddcw21 ibd2sql_v1.0]#python test.py /data/mysql_3314/mysqldata/ibd2sql/t20240102_js.ibd 
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (1, 'aa', '{"t1": 123}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (2, 'aa', '{"t1": "AAAAAAAAAAAAAAAAACBBBBBBBBBBBB"}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (3, 'aa', '{"a1": {"b1": "b1"}, "a2": "a6"}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (4, 'bb', '{"t1": "ABC"}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (5, 'bb', '{"t1": false}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (6, 'bb', '[{"AA": "AA"}, {"BB": "BB"}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (7, 'bb', '[{"AA": {"CC": "CC"}}, {"BB": "BB"}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (8, 'bb', '[{"AA": {"CC": {"DD": "DD"}}}, {"BB": "BB"}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (9, 'bb', '[{"AA": {"CC": {"DD": "DD"}}}, {"BB": "BB"}, {"FF": 2}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (10, 'bb', '[{"13": {"CC": {"DD": "DD"}}}, {"BB": "BB"}, {"FF": 2}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (11, 'bb', '{"aa": true, "bb": false, "cc": null}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (12, 'cc', '{"aa": 1, "bb": 2, "cc": 0}');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (13, 'cc', '[{"aa": 1}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (14, 'cc', '[{"13": {"CC": {"DD": "DD"}}}, {"BB": "BB"}, {"FF": 2}]');
REPLACE INTO `db2`.`t20240102_js`(`id`,`name`,`aa`) VALUES (15, 'cc', '{"a1": {"b1": "b1"}, "a2": "a6"}');

我这里做了schma替换, 所以要验证的话, 得再建一张表.

代码语言:sql
复制
Query OK, 1 row affected (0.00 sec)

(root@127.0.0.1) [(none)]> checksum table db2.t20240102_js, ibd2sql.t20240102_js;
+----------------------+------------+
| Table                | Checksum   |
+----------------------+------------+
| db2.t20240102_js     | 3802174698 |
| ibd2sql.t20240102_js | 3802174698 |
+----------------------+------------+
2 rows in set (0.00 sec)

(root@127.0.0.1) [(none)]> 

总结

1. mysql的json是mysql层实现的, 所以字节序是小端 (innodb的基本上都是大端)

2. mysql插入json数据的时候是严格要求json格式的, 即外面是单引号, 里面是双引号. 而且布尔类型和null都是小写.

3. mysql的json类型是标准json类型, 所以使用json包处理的数据是可以直接写入mysql数据库的

参考: mysql源码 sql/json_binary.h 和 RFC7159

注; ibd2sql 1.0 版本 1月底发布, 新增支持debug功能,分区表,json等 和 当前的v0.3版本有丢丢(指尖宇宙.jpg)区别

mysql变长类型读取

mysql的变长类型读取和innodb的变长类型记录有区别(innodb是256*256=64K)

当第一字节 小于128字节时, 就使用1字节存储大小, 直接 读那1字节即可

当第一字节 大于等于 128时候 (即第一bit为1) 就使用2字节表示. 此时 数据为 第一字节 减去 128 (去掉第一bit) 然后加上第二字节*128 即为最终大小.

代码语言:python
代码运行次数:0
复制
-----------------------------------------------------
| 1 bit flag | 7 bit data | if flag, 8 bit data*128 |
-----------------------------------------------------

相关解析代码可以参考如下:

代码语言:python
代码运行次数:0
复制
	def read_var(self,offset):
		_s = int.from_bytes(self.bdata[offset:offset+1],'little')
		size = 1
		if _s & (1<<7):
			size += 1
			_s = self.bdata[offset:offset+2]
			_t = int.from_bytes(_s[1:2],'little')*128 + int.from_bytes(_s[:1],'little')-128
		else:
			_t = _s
			
		return size,_t

附脚本

很多注释没有删, 调试的时候 有个t 写成了selt.t 导致满天的print 去找问题.... 后面也懒得删了. 方便后面接入debug

代码语言:python
代码运行次数:0
复制
#@mysql sql/json_binary.h
import struct
import sys

_ = """
                                                               - -----------------
                                                              | JSON OBJECT/ARRAY |
                                                               - -----------------
                                                                      |
 -------------------------------------------------------------------------
| TYPE | ELEMENT_COUNT | KEY-ENTRY(if object) | VALUE-ENTRY | KEY | VALUE |
 -------------------------------------------------------------------------
                               |                    |          |
                               |                    |     --------------
                   --------------------------       |    | UTF8MB4 DATA |
                  | KEY-OFFSET |  KEY-LENGTH |      |     --------------
                   --------------------------       |
                                                    |
                                         --------------------------------
                                         | TYPE | OFFSET/VALUE(if small) |
                                         --------------------------------

small 2 bytes   large 4 bytes
---------------------------------------------------
TYPE          1 byte
COUNT         2/4 bytes
SIZE          2/4 bytes
VALUE         VALUE/OBJECT/ARRAY
---------------------------------------------------

---------------------------------------------------
OBJECT VALUE = KEY_ENTRY + VALUE_ENTRY + KEY + VALUE  #KEY肯定是字符串, 所以不需要记录数据类型
ARRAY  VALUE = VALUE_ENTRY + VALUE #不需要KEY

KEY_ENTRY   = KEY_OFFSET(2/4bytes) + KEY_LNGTH(2 bytes)
VALUE_ENTRY = TYPE(1byte) + OFFSET(2/4 bytes)/VALUE  (如果类型是int,literal之类的,就直接是值了, 否则就走OFFSET)
---------------------------------------------------

"""

#  type ::=
#      0x00 |       // small JSON object
#      0x01 |       // large JSON object
#      0x02 |       // small JSON array
#      0x03 |       // large JSON array
#      0x04 |       // literal (true/false/null)
#      0x05 |       // int16
#      0x06 |       // uint16
#      0x07 |       // int32
#      0x08 |       // uint32
#      0x09 |       // int64
#      0x0a |       // uint64
#      0x0b |       // double
#      0x0c |       // utf8mb4 string
#      0x0f         // custom data (any MySQL data type)


#  value ::=
#      object  |
#      array   |
#      literal |
#      number  |
#      string  |
#      custom-data

class jsonob(object):
	def __init__(self,bdata,t):
		"""
		bdata = json data
		t 类型 json类型
		"""
		self.bdata = bdata
		self.t = t
		self.offset = 0
		self.ssize = 2 if self.t == 0x00 or self.t == 0x02 else 4
		self._type = None
		self._bdata = b''
		#print("BEGIN JSON TO B, CURRENT TYPE:",self.t)

	def read_key_entry(self):
		"""
		read key-entry
		"""
		#print("READ KEY ENTRY")
		key_entry = []
		for x in range(self.element_count):
			key_offset = self.read_little()
			key_length = self.read_little(2)
			key_entry.append((key_offset,key_length))
		self.key_entry = key_entry

	def read_value_entry(self):
		#print("READ VALUE ENTRY")
		value_entry = []
		for x in range(self.element_count):
			t = self.read_little(1)
			#print("\t entry: type:",t)
			data = None
			if t < 0x04:
				#print("READ VALUE ENTRY JSON object/array")
				data  = self.read_little()
			elif t == 0x04: #literal
				#print("READ VALUE ENTRY literal")
				_data = self.read_little()
				if _data == 1:
					data = True
				elif _data == 2:
					data = False
				elif _data == 0:
					data = None
				else:
					data = ''
			elif t >= 0x05 and t <= 0x0a: #inline data
				#print("READ VALUE ENTRY Inline data for INT",t,0x05,0x0a)
				data = self.read_inline_data(t)
			elif t == 0x0b: #double
				#print("READ VALUE ENTRY Double")
				#data = struct.unpack('d',self.read(8))[0]
				data = self.read_little()
			elif t == 0x0c: #string
				#print("READ DATA ENTRY STRING",self.offset)
				data = self.read_little() #OFFSET
			value_entry.append((t,data))
		self.value_entry = value_entry
		#print("VALUE ENTRY LIST ---------",self.value_entry)

	def read_key(self):
		#print("READ KEY")
		key = []
		for x in self.key_entry:
			key.append(self.bdata[x[0]:x[0]+x[1]].decode()  )
		self.key = key

	def read_value(self):
		#print("READ VALUE")
		value = []
		for x in self.value_entry:
			#print("VALUE TYPE:xxxxxxx",x[0])
			if x[0] == 0x0c: #字符串
				_s,size = self.read_var(x[1])
				#size = int.from_bytes(self.bdata[x[1]:x[1]+1],'little') #先都按1字节计算
				value.append(self.bdata[x[1]+_s:x[1]+_s+size].decode()) 
			elif x[0] == 0x0b:
				value.append(struct.unpack('d',self.bdata[x[1]:x[1]+8])[0])
			elif x[0] <= 0x03: #json对象, 又递归
				s = self.ssize
				size = int.from_bytes(self.bdata[x[1]+s: x[1]+s+s ], 'little')
				data = self.bdata[x[1]:x[1]+size]
				_aa = jsonob(data,x[0])
				value.append(_aa.init())
			else:
				value.append(x[1])
		self.value = value
				
	def read_var(self,offset):
		"""
		读mysql的varchar的 记录长度的大小, 范围字节数量和大小
		如果第一bit是1 就表示要使用2字节表示:
			后面1字节表示 使用有多少个128字节, 然后加上前面1字节(除了第一bit)的数据(0-127) 就是最终数据
-----------------------------------------------------
| 1 bit flag | 7 bit data | if flag, 8 bit data*128 |
-----------------------------------------------------
		"""
		_s = int.from_bytes(self.bdata[offset:offset+1],'little')
		size = 1
		if _s & (1<<7):
			size += 1
			_s = self.bdata[offset:offset+2]
			_t = int.from_bytes(_s[1:2],'little')*128 + int.from_bytes(_s[:1],'little')-128
		else:
			_t = _s
			
		return size,_t


	def init(self,):
		#print(self.bdata)
		self.element_count = self.read_little()
		#print("ELEMENT COUNT:",self.element_count)
		#print(self.read_little())
		self._size = self.read_little()
		#print(f"THIS OBJECT SIZE:",self._size, "ACTUAL SIZE:",len(self.bdata))
		if self._size != len(self.bdata):
			return None
		#print("WILL INIT")
		if self.t == 0x00 or self.t == 0x01: #object
			self._type = "JSON Object"
			#print(f"THIS TYPE IS {self._type}")
			self.data = {}
			self.read_key_entry()
			self.read_value_entry()
			self.read_key()
			self.read_value()
			self.data = {k:v for k,v in zip(self.key,self.value)}

		elif self.t == 0x02 or self.t == 0x03: #array
			self._type = "JSON Array"
			#print(f"THIS TYPE IS {self._type}")
			self.data = []
			self.read_value_entry()
			self.read_value()
			self.data = self.value
		return self.data
			

	def read_little(self,ssize=None):
		ssize = self.ssize if ssize is None else ssize
		s = int.from_bytes(self.read(ssize),'little')
		#print(f"READ LITTLE SIZE: {ssize} bytes  bdata:{self._bdata} value:{s} ")
		return s

	def read(self,n):
		_t = self.bdata[self.offset:self.offset+n]
		self.offset += n
		self._bdata = _t
		return _t

	def _read_int(self,n):
		data = self.read(n)
		return int.from_bytes(data,'big')

	def read_uint(self,n,is_unsigned=True):
		_t = self._read_int(n)
		_s = n*8 - 1
		#print("read uint",self._bdata,_t,_s)
		return (_t&((1<<_s)-1))-2**_s if _t < 2**_s and not is_unsigned else (_t&((1<<_s)-1))

	def read_int(self,n):
		return self.read_uint(n,False)

	def read_inline_data(self,t):
		n = 0
		is_unsigned = True
		#print("\tread_inline_data TYPE:",t)
		if t == 0x05: #int16
			n = 2
		elif t == 0x06: #uint16
			n = 2
			is_unsigned = True
		elif t == 0x07: #int32
			n = 4
		elif t == 0x08: #uint32
			n = 4
			is_unsigned = True
		elif t == 0x09: #int64
			n = 8
		elif t == 0x0a: #uint64
			n = 8
			is_unsigned = True
		#return self.read_uint(n,is_unsigned)
		signed = False if is_unsigned else True
		rs = int.from_bytes(self.read(n),'little',signed=signed)
		#print("\tINLINE DATA:",rs)
		return rs

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JSON磁盘上的格式
  • 困难点
  • 使用脚本解析
  • 总结
  • mysql变长类型读取
  • 附脚本
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档