動(dòng)態(tài)列
云原生多模數(shù)據(jù)庫 Lindorm寬表引擎支持動(dòng)態(tài)列功能,即創(chuàng)建表時(shí)未顯式指定的列,在實(shí)際業(yè)務(wù)中動(dòng)態(tài)寫入數(shù)據(jù)并執(zhí)行查詢。本文介紹如何通過Lindorm SQL實(shí)現(xiàn)動(dòng)態(tài)列能力的開啟、寫入以及查詢。
背景信息
傳統(tǒng)關(guān)系型數(shù)據(jù)庫表的Schema必須預(yù)先定義,如果要增加列,則需要變更表屬性。變更大表的表屬性是一個(gè)非常耗時(shí)的操作。同時(shí),預(yù)定義的Schema給業(yè)務(wù)的設(shè)計(jì)會(huì)帶來很多不便。但是Lindorm寬表引擎原生支持動(dòng)態(tài)列,列無需提前定義,您可以直接使用Lindorm SQL來對(duì)動(dòng)態(tài)列進(jìn)行讀寫操作。
注意事項(xiàng)
如果您需要使用動(dòng)態(tài)列功能,請(qǐng)注意以下幾點(diǎn):
確保云原生多模數(shù)據(jù)庫 Lindorm寬表引擎為2.2.19及以上版本,具體操作請(qǐng)參見升級(jí)小版本。
Lindorm寬表中動(dòng)態(tài)列類型均為VARBINARY,查詢動(dòng)態(tài)列和寫入動(dòng)態(tài)列時(shí)都必須將動(dòng)態(tài)列類型轉(zhuǎn)化成字節(jié)數(shù)組。
通過Lindorm SQL管理動(dòng)態(tài)列,查詢動(dòng)態(tài)列和寫入動(dòng)態(tài)列操作支持以云數(shù)據(jù)庫HBase兼容方式創(chuàng)建和寫入的表。
開啟動(dòng)態(tài)列
一張表的動(dòng)態(tài)列功能一旦啟用則無法關(guān)閉。
動(dòng)態(tài)列的開啟可以通過以下兩種方式:
在創(chuàng)建表格時(shí)通過
WITH
子句開啟動(dòng)態(tài)列功能。CREATE TABLE t_dynamic (p1 INT, c1 INT, c2 VARCHAR, PRIMARY KEY(p1)) WITH (DYNAMIC_COLUMNS='true');
通過修改表的屬性開啟動(dòng)態(tài)列功能。
ALTER TABLE t_dynamic SET 'DYNAMIC_COLUMNS' = 'true';
結(jié)果驗(yàn)證
您可以通過以下語句驗(yàn)證表是否已成功開啟動(dòng)態(tài)列功能。
SHOW TABLE VARIABLES FROM t_dynamic LIKE 'DYNAMIC_COLUMNS';
說明開啟動(dòng)態(tài)列后,您可以修改表的屬性或者在表的Schema中增加新的列,例如執(zhí)行以下語句表示新增c3列,數(shù)據(jù)類型為INT。
ALTER TABLE t_dynamic ADD COLUMN c3 int;
如果您在此之前寫入過名為
c3
的動(dòng)態(tài)列,由于寫入的數(shù)據(jù)類型均為VARBINARY,查詢數(shù)據(jù)和寫入數(shù)據(jù)時(shí)會(huì)拋出異常,如果新增列的數(shù)據(jù)類型為VARBINARY就不會(huì)出現(xiàn)這個(gè)異常。因此,變更動(dòng)態(tài)列表的Schema時(shí)需要注意數(shù)據(jù)類型不同的情況,盡量避免預(yù)定義列和動(dòng)態(tài)列重名。
寫入動(dòng)態(tài)列
SQL文本寫入
寫入動(dòng)態(tài)列表的語法與寫入普通列表的語法一致,開啟動(dòng)態(tài)列之后,可以寫入沒有預(yù)先在Schema中定義的列,但是動(dòng)態(tài)列的類型只能為VARBINARY(即字節(jié)數(shù)組)。Lindorm支持用戶使用Lindorm-cli直接以SQL文本的方式將數(shù)據(jù)寫入動(dòng)態(tài)列,此時(shí)UPSERT語句中指定的動(dòng)態(tài)列的值必須為數(shù)據(jù)的十六進(jìn)制字符串形式(即使用十六進(jìn)制字符(0-9, A-F)來表示二進(jìn)制數(shù)據(jù)的形式。以下簡稱HexString)。
一個(gè)字節(jié)的范圍是0~255,十六進(jìn)制表示為0x00~0xFF。對(duì)于一個(gè)字節(jié)數(shù)組{0x00, 0xFF},它的HexString就是00FF,將字節(jié)數(shù)組轉(zhuǎn)換為HexString的代碼請(qǐng)參見附錄:字節(jié)數(shù)組轉(zhuǎn)換為HexString的實(shí)現(xiàn)示例。
寫入動(dòng)態(tài)列的場景示例如下:
執(zhí)行以下語句在
t_dynamic
表中寫入c3列,c3列為動(dòng)態(tài)列,寫入成功。UPSERT INTO t_dynamic (p1, c2, c3) VALUES (1, '1', '41');
執(zhí)行以下語句在
t_dynamic
表中寫入c4列,c4列為動(dòng)態(tài)列,寫入成功。UPSERT INTO t_dynamic (p1, c4) VALUES (2, 'ef0011');
在SQL引擎2.6.8及以上的版本中,為了避免混淆普通字符串和HexString,支持在SQL文本中通過下述形式指定HexString。
UPSERT INTO t_dynamic(p1, c4) VALUES (3, x'ef0011');
上述語句保存的動(dòng)態(tài)列
c4
的數(shù)據(jù)實(shí)際是0xEF
、0x00
和0x11
這三個(gè)字節(jié),而非ef0011
這個(gè)字符串(ef0011
字符串需要占用6個(gè)字節(jié)的存儲(chǔ)空間)。說明如何查看Lindorm SQL的版本,請(qǐng)參見SQL版本說明。
執(zhí)行以下語句在
t_dynamic
表中寫入c5列,c5列為動(dòng)態(tài)列。由于動(dòng)態(tài)列c5的值f
不是偶數(shù)長度的HexString,所以寫入失敗,需要將f
修改為0f
。UPSERT INTO t_dynamic (p1, c5) VALUES (4, 'f');
執(zhí)行以下語句在
t_dynamic
表中寫入動(dòng)態(tài)列c6,但由于指定的值gf
不是HexString,所以該語句執(zhí)行時(shí)報(bào)錯(cuò)。UPSERT INTO t_dynamic (p1, c6) VALUES (5, x'gf');
SQL參數(shù)化寫入(推薦)
相較于上述直接通過SQL文本直接寫動(dòng)態(tài)列,更推薦的做法是在應(yīng)用程序中通過SQL綁參的方式綁定要寫入的字節(jié)數(shù)組實(shí)現(xiàn)寫入。如果您需要將字符串或者數(shù)值型的值寫入動(dòng)態(tài)列中,那么需要先將這些值編碼成字節(jié)數(shù)組,然后通過綁參寫入。
以表t_dynamic
為例,使用Java代碼綁參寫入動(dòng)態(tài)列的代碼示例如下:
Connection conn = DriverManager.getConnection(lindorm-jdbc-url);
String createTable = "CREATE TABLE testTable (p1 VARCHAR, c1 INT, PRIMARY KEY(p1)) 'DYNAMIC_COLUMNS' = 'true'";
Statement statement = conn.createStatement();
statement.execute(createTable);
//插入3列,其中p1,c1為Schema預(yù)先定義好的列,c2沒有預(yù)先定義,為動(dòng)態(tài)列寫入。
String sqlUpsert = "upsert into " + tableName + "(p1, c1, c2) values(?, ?, ?)";
try (PreparedStatement stmt = conn.prepareStatement(sqlUpsert)) {
stmt.setString(1, "pk");
stmt.setInt(2, 4);
stmt.setBytes(3, new byte[] {0,1});
int updated = stmt.executeUpdate();
Assert.assertEquals(1, updated);
}
使用綁參方式寫入數(shù)據(jù)時(shí),理論上可以通過類似JDBC中的PreparedStatement#setString( )
方法傳入一個(gè)HexString來寫入數(shù)據(jù),但強(qiáng)烈建議不要這樣操作,尤其是使用MySQL協(xié)議與Lindorm進(jìn)行交互時(shí)。因?yàn)樵贛ySQL協(xié)議中,客戶端傳入的字符串型參數(shù)會(huì)以字節(jié)數(shù)組的形式傳至服務(wù)端,可能導(dǎo)致數(shù)據(jù)的二義性,因此強(qiáng)烈建議避免這種操作。
查詢動(dòng)態(tài)列
查詢動(dòng)態(tài)列的場景分為以下幾種:
顯式指定查詢字段為動(dòng)態(tài)列。查詢動(dòng)態(tài)列表的語法與查詢普通列表的語法一致,開啟動(dòng)態(tài)列之后,可以查詢沒有預(yù)先在Schema中定義的列,如下示例,c3和c4是創(chuàng)建表后新增的動(dòng)態(tài)列。
SELECT p1, c2, c3, c4 FROM t_dynamic WHERE p1 = 1;
返回結(jié)果如下:
+----+----+------+------+ | p1 | c2 | c3 | c4 | +----+----+------+------+ | 1 | 1 | 0x41 | null | +----+----+------+------+
不確定表中已包含哪些動(dòng)態(tài)列時(shí),可以使用
SELECT *
查詢動(dòng)態(tài)列表。Lindorm SQL為了保證結(jié)果集元數(shù)據(jù)的正確性,強(qiáng)制要求在此類查詢語句后添加LIMIT
子句來限定結(jié)果集的大小。SELECT * FROM t_dynamic LIMIT 10;
返回結(jié)果如下:
+----+------+------+------+----------+ | p1 | c1 | c2 | c3 | c4 | +----+------+------+------+----------+ | 1 | null | 1 | 0x41 | null | | 2 | null | null | null | 0xef0011 | | 3 | null | null | null | 0xef0011 | +----+------+------+------+----------+
重要對(duì)于動(dòng)態(tài)列表的
SELECT *
查詢操作,LIMIT的默認(rèn)最大值為5000,超過最大值會(huì)報(bào)錯(cuò)。在
WHERE
條件中使用動(dòng)態(tài)列。為了確保查詢語句的性能
WHERE
條件中需包含主鍵或索引列,如果您希望在查詢的過濾條件中使用動(dòng)態(tài)列進(jìn)行過濾,那么使用SQL文本查詢時(shí),動(dòng)態(tài)列的過濾條件必須指定為HexString;使用綁參方式進(jìn)行查詢時(shí),過濾條件建議直接指定為原始的字節(jié)數(shù)組。例如表
t_dynamic
中c4
為動(dòng)態(tài)列,那么查詢成功的語句如下示例:SELECT p1, c4 FROM t_dynamic WHERE p1 = 3 AND c4 = x'ef0011';
作為對(duì)比,下述查詢示例中由于給動(dòng)態(tài)列c4指定的過濾條件
1
不是一個(gè)HexString,所以該查詢執(zhí)行失敗。SELECT p1, c1, c4 FROM t_dynamic WHERE p1 = 2 AND c4 = '1';
動(dòng)態(tài)列數(shù)據(jù)的顯示
使用不同的命令行工具時(shí),動(dòng)態(tài)列的查詢結(jié)果顯示方式不同,具體如下:
Lindorm-cli
HexString
Lindorm-cli會(huì)以HexString的形式展示動(dòng)態(tài)列的查詢結(jié)果。以表t_dynamic
為例,查詢語句如下:
SELECT p1, c3, c4 FROM t_dynamic WHERE p1 = 1;
返回結(jié)果如下:
+----+------+------+------+----------+
| p1 | c1 | c2 | c3 | c4 |
+----+------+------+------+----------+
| 1 | null | 1 | 0x41 | null |
| 2 | null | null | null | 0xef0011 |
| 3 | null | null | null | 0xef0011 |
+----+------+------+------+----------+
上述結(jié)果集中動(dòng)態(tài)列c3
的第一行查詢結(jié)果0x41
表示字母A。
字符串格式
如果您期望查詢結(jié)果以字符串的格式返回,需要通過Lindorm-cli的連接語句末尾添加-bytesOutputAsString參數(shù),示例如下:
./lindorm-cli -url <jdbc url> -username <用戶名> -password <密碼> -bytesOutputAsString
連接參數(shù)說明請(qǐng)參見步驟二:連接Lindorm寬表引擎。
執(zhí)行相同查詢語句SELECT p1, c3, c4 FROM t_dynamic WHERE p1 = 1;
,將返回如下結(jié)果:
+-----+-------+---------+
| p1 | c3 | c4 |
+-----+-------+---------+
| 1 | A | null |
+-----+-------+---------+
MySQL命令行
MySQL命令行并不會(huì)默認(rèn)顯示動(dòng)態(tài)列的數(shù)據(jù),這些二進(jìn)制數(shù)據(jù)將會(huì)以?
表示。
附錄:字節(jié)數(shù)組轉(zhuǎn)換為HexString的實(shí)現(xiàn)示例
在Java中,使用下述代碼可以將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換為HexString。
private static final char[] DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
private static String toHexString(byte[] bytes) {
char[] chars;
int j = 0;
chars = new char[bytes.length * 2];
for (byte b : bytes) {
chars[j++] = DIGITS[(b & 0xF0) >> 4];
chars[j++] = DIGITS[b & 0x0F];
}
return new String(chars, 0, j);
}
public void testToHexString() {
String s = "Hello, world";
// 對(duì)于字符串類型,可以直接使用String的getBytes方法獲得對(duì)象對(duì)應(yīng)的byte[]
byte[] bytes = s.getBytes(Charset.forName("UTF-8"));
String hexString = toHexString(bytes);
System.out.println(hexString); //打印結(jié)果為: 48656c6c6f2c20776f726c64
}