AliSQL情人節版本Release:開源Sequence Engine
AliSQL開源Sequence Engine
Introduction
單調遞增的唯一值,是在持久化資料庫系統中常見的需求,無論是單節點中的業務主鍵,還是分散式系統中的全局唯一值,亦或是多系統中的冪等控制。不同的資料庫系統有不同的實現方法,比如MySQL提供的AUTO_INCREMENT,Oracle,SQL Server提供SEQUENCE等。
在MySQL資料庫中,如果業務系統希望封裝唯一值,比如增加日期,用戶等信息,AUTO_INCREMENT的方法會帶來很大的不便,在實際的系統設計的時候, 也存在不同的折中方法,比如:
- 序列值由Application或者Proxy來生成,不過弊端很明顯,狀態帶到應用端,增加了擴容和縮容的複雜度。
- 序列值由資料庫通過模擬的表來生成,但需要中間件來封裝和簡化獲取唯一值的邏輯。
AliSQL自主實現了SEQUENCE ENGINE,通過引擎的設計方法,儘可能的兼容其他資料庫的使用方法,簡化獲取序列值複雜度。
Github開源地址:alibaba/AliSQL: AliSQL is a MySQL branch originated from Alibaba Group. Fetch document from Release Notes at bottom.
Description
AliSQL開源的SEQUENCE,實現了MySQL存儲引擎的設計介面,但底層的數據仍然使用現有的存儲引擎,比如InnoDB或者MyISAM來保存持久化數據,以便儘可能的保證現有的外圍工具比如XtraBackup等工具的兼容,所以SEQUENCE ENGINE僅僅是一個邏輯引擎。
對sequence對象的訪問通過SEQUENCE handler介面,這一層邏輯引擎主要實現NEXTVAL的滾動,CACHE的管理等,最後透傳給底層的基表數據引擎,實現最終的數據訪問。
下面我們透過語法來看下AliSQL SEQUENCE的使用。
Syntax
1. CREATE SEQUENCE Syntax:
CREATE SEQUENCE [IF NOT EXISTS] schema.sequence_name [START WITH <constant>] [MINVALUE <constant>] [MAXVALUE <constant>] [INCREMENT BY <constant>] [CACHE <constant> | NOCACHE] [CYCLE | NOCYCLE] ;
SEQUENCE OPTIONS:
- STARTSequence的起始值
- MINVALUE
Sequence的最小值,如果這一輪結束並且是cycle的,那麼下一輪將從MINVALUE開始
- MAXVALUESequence的最大值,如果到最大值並且是nocycle的,那麼將會得到以下報錯:ERROR HY000: Sequence db.seq has been run out.
- INCREMENT BYSequence的步長
- CACHE/NOCACHECache的大小,為了性能考慮,可以設置cache的size比較大,但如果遇到實例重啟,cache內的值會丟失
- CYCLE/NOCYCLE表示sequence如果用完了後,是否允許從MINVALUE重新開始
例如:
create sequence s start with 1 minvalue 1 maxvalue 9999999 increment by 1 cache 20 cycle;
2. SHOW SEQUENCE Syntax
SHOW CREATE [TABLE|SEQUENCE] schema.sequence_name;CREATE SEQUENCE schema.sequence_name ( `currval` bigint(21) NOT NULL COMMENT current value, `nextval` bigint(21) NOT NULL COMMENT next value, `minvalue` bigint(21) NOT NULL COMMENT min value, `maxvalue` bigint(21) NOT NULL COMMENT max value, `start` bigint(21) NOT NULL COMMENT start value, `increment` bigint(21) NOT NULL COMMENT increment value, `cache` bigint(21) NOT NULL COMMENT cache size, `cycle` bigint(21) NOT NULL COMMENT cycle state, `round` bigint(21) NOT NULL COMMENT already how many round) ENGINE=InnoDB DEFAULT CHARSET=latin1
由於SEQUENCE是通過真正的引擎表來保存的,所以SHOW COMMAND看到仍然是engine table。
3. QUERY STATEMENT Syntax
SELECT [NEXTVAL | CURRVAL | *] FROM schema.sequence_name;SELECT [NEXTVAL | CURRVAL | *] FOR schema.sequence_name;
這裡支持兩種訪問方式,FROM和FOR:
- FROM clause: 兼容正常的SELECT查詢語句,返回的結果是基表的數據,不迭代NEXTVAL。
- FOR clause:兼容SQL Server的方法,返回的結果是迭代後NEXTVAL的值。
mysql> select * from s;+---------+---------+----------+---------------------+-------+-----------+-------+-------+-------+| currval | nextval | minvalue | maxvalue | start | increment | cache | cycle | round |+---------+---------+----------+---------------------+-------+-----------+-------+-------+-------+| 0 | 30004 | 1 | 9223372036854775807 | 1 | 1 | 10000 | 0 | 0 |+---------+---------+----------+---------------------+-------+-----------+-------+-------+-------+1 row in set (0.00 sec)mysql> select * for s;+---------+---------+----------+---------------------+-------+-----------+-------+-------+-------+| currval | nextval | minvalue | maxvalue | start | increment | cache | cycle | round |+---------+---------+----------+---------------------+-------+-----------+-------+-------+-------+| 0 | 20014 | 1 | 9223372036854775807 | 1 | 1 | 10000 | 0 | 0 |+---------+---------+----------+---------------------+-------+-----------+-------+-------+-------+
4. 兼容性
因為要兼容MYSQLDUMP的備份方式,所以支持另外一種CREATE SEQUENCE方法,即:通過創建SEQUENCE表和INSERT一行初始記錄的方式, 比如:
CREATE SEQUENCE schema.sequence_name ( `currval` bigint(21) NOT NULL COMMENT current value, `nextval` bigint(21) NOT NULL COMMENT next value, `minvalue` bigint(21) NOT NULL COMMENT min value, `maxvalue` bigint(21) NOT NULL COMMENT max value, `start` bigint(21) NOT NULL COMMENT start value, `increment` bigint(21) NOT NULL COMMENT increment value, `cache` bigint(21) NOT NULL COMMENT cache size, `cycle` bigint(21) NOT NULL COMMENT cycle state, `round` bigint(21) NOT NULL COMMENT already how many round) ENGINE=InnoDB DEFAULT CHARSET=latin1INSERT INTO schema.sequence_name VALUES(0,0,1,9223372036854775807,1,1,10000,1,0);COMMIT;
但強烈建議使用native的CREATE SEQUENCE方法。
5. 語法限制
- Sequence不支持subquery和join
- FOR clause只支持sequence表,普通引擎表不支持
- 可以使用SHOW CREATE TABLE或者SHOW CREATE SEQUENCE來訪問SEQUENCE結構,但不能使用SHOW CREATE SEQUENCE訪問普通表
- 不支持CREATE TABLE的時候指定SEQUENCE引擎,sequence表只能通過CREATE SEQUENCE的語法來創建
High level architecture
1. Sequence initialization
Sequence對象的創建,會轉化成擁有固定[CURRVAL, NEXTVAL, MINVALUE, MAXVALUE, START, INCREMENT, CACHE, CYCLE, ROUND]這9個欄位的引擎表,並根據CREATE SEQUENCE clause的定義,初始化了一條數據,所以sequence對象實質上是擁有一條記錄的存儲引擎表,SLAVE複製的BINLOG使用CREATE SEQUENCE ...語句生成的QUERY EVENT來完成。
2. Sequence interface
SEQUENCE handler實現了一部分的handler interface,並定義了兩個重要的屬性,SEQUENCE_SHARE和BASE_TABLE_FILE,SEQUENCE_SHARE保存著共享的sequence對象屬性和CACHE的值,NEXTVAL的值首先從cache中獲取,只有在cache使用完了,才會查詢基表。
BASE_TABLE_FILE是基表的handler,對持久化的數據的訪問和修改,都通過BASE_TABLE_FILE handler進行訪問。3. Sequence cache
Sequence對象的CACHE值保存在SEQUENCE_SHARE中,使用SEQUENCE_SHARE::MUTEX進行保護,所有對cache的訪問是串列的。比如cache size是20,那麼SEQUENCE_SHARE中只是保存一個cache_end值,當訪問的NEXTVAL到了cache_end,就會從基表中獲取下一個batch放到cache中。NEXTVAL根據INCREMENT BY設置的步長進行迭代。
4. Sequence update
當cache用完了之後,會從基表中獲取下一個batch,這樣會更新基表中的記錄,查詢會轉化成更新語句,
其更新的主要步驟如下:- 升級SEQUENCE的MDL_SHARE_READ METADATA LOCK 到 MDL_SHARE_WRITE級別
- 持有GLOBAL MDL_INTENSIVE_EXCLUSIVE METADATA LOCK
- 開啟AUTONOMOUS TRANSACTION
- 更新記錄並生成BINLOG EVENT
- 持有COMMIT METADATA LOCK
- XA提交AUTONOMOUS TRANSACTION 並釋放MDL鎖
5. Autonomous transaction
因為nextval不支持ROLLBACK重用,所以必須重啟一個自治事務來脫離事務上下文,
其步驟如下:- 備份當前基表引擎的事務上下文
- 備份當前BINLOG引擎的上下文
- SEQUENCE和BINLOG分別註冊AUTONOMOUS TRANSACTION
- 等更新完成,XA提交AUTONOMOUS TRANSACTION
- 還原當前事務上下文
6. Sequence read only
因為SEQUENCE的SELECT語句會轉換成UPDATE語句,所以SELECT NEXTVAL FOR s statement須持有 MDL_SHARE_WRITE 和 GLOBAL MDL_INTENSIVE_EXCLUSIVE METADATA LOCK 進行,以便在READ ONLY的時候,阻塞對sequence對象的訪問。
7. Skip cache
這裡指兩種CACHE:
- 一種是SEQUENCE的CACHE,可以使用SELECT NEXTVAL FORM Sequence_name來skip。
- 另外一種是QUERY CACHE,所有的SEQUENCE都設置了不支持QUERY CACHE,這樣避免由於QUERY CACHE導致NEXTVAL沒有迭代。
8. Sequence backup
由於SEQUENCE是通過真正的引擎表來保存的,所以類似XtraBackup這樣的物理備份可以直接使用,而類似於MYSQLDUMP這樣的邏輯備份,SEQUENCE會備份成CREATE SEQUENCE語句和INSERT語句的組合來完成。
Next Release
本次開源了部分功能,下一次release將繼續開源SEQUENCE的部分功能:
- 支持CURRVAL的訪問,CURRVAL表示當前session的上一次的NEXTVAL訪問的值。
- 兼容更多資料庫的訪問方法,比如:
Oracle Syntax: SELECT sequence_name.nextval FROM DUAL;PostgreSQL Syntax: nextval(regclass); currval(regclass); setval(regclass, bigint);
Usage Scenario
1. 更具有業務含義的主鍵設計 .
例如:[八位日期 + 四位USER ID + sequence_number]的流水業務單據號的設計格式,可以通過SELECT NEXTVAL FOR Sequence和應用封裝的方式實現,相比較無意義的id數字,這種格式會帶來幾個優勢:
- 保持和時間同步的有序性,有利於數據的歸檔,比如可以直接使用這種ID來進行按日/月/年RANGE分區, 無縫使用MySQL的partition特性
- 增加USER的id信息,可以作為天然的分庫分表邏輯位, 提升數據節點可擴展性
- 保持數字的有序性,保證InnoDB這種聚簇索引表的插入性能穩定
業界目前採用的設計方法:
- Booking使用了AUTO_INCREMENT的方法, 先插入一個無業務含義的數字,然後使用last_insert_id()方法獲取ID值,最後在業務邏輯中使用這個ID值。 其劣勢就是必須先插入,並沒有辦法再修改這個無業務含義的id。
- Twitter採用了另外一種格式,[41 bits timestamp + 10 bits configured machine ID + 12 bits sequence number], sequence number的生成機制沒有透露,machine ID的的設計,使用Zookeeper來管理的machine ID或者機器的MAC address。
- UUID的方法,這種方式生成了一個隨機的唯一值,嚴重影響了插入的性能,並且增大了索引大小,降低了命中率,沒有任何優勢。
2. 分散式節點的唯一值設計
分散式SEQUENCE生成:
- 可以為每一個節點設計sequence,比如為每個節點設計不同的INCREMENT BY步長來達到MySQL AUTO_INCREMENT中,設置auto_increment_increment和auto_increment_offset的效果,但相比較auto increment的全局配置,並且保存在my.cnf中的方法,SEQUENCE可以把這些配置當做sequence對象的屬性持久化保存下來,優勢明顯。但不推薦使用這種方法來設計唯一值,會給運維留下不少坑。
- 使用類似twitter的方法,每一個節點上創建sequence,然後增加節點信息到sequence number中,生成唯一值。
集中式SEQUENCE生成:
- 對於分散式節點中的ID需求,使用獨立的集中式的sequence服務來生成,但如果要保證持續可用,sequence服務仍然需要設計成多節點的,比如Flickr的Ticket Servers設計:
Sequence服務節點上創建Ticket表:
CREATE TABLE `Tickets64` ( `id` bigint(20) unsigned NOT NULL auto_increment, `stub` char(1) NOT NULL default , PRIMARY KEY (`id`), UNIQUE KEY `stub` (`stub`)) ENGINE=MyISAM+-------------------+------+| id | stub |+-------------------+------+| 72157623227190423 | a |+-------------------+------+
使用以下語句,生成ID值:
REPLACE INTO Tickets64 (stub) VALUES (a);SELECT LAST_INSERT_ID();
因為PHOTOS,COMMENTS,FAVORITES,TAGS都需要ID, 所以會建不同的ticket表來完成,為了保持持續可用,採用了:
TicketServer1:auto-increment-increment = 2auto-increment-offset = 1TicketServer2:auto-increment-increment = 2auto-increment-offset = 2
來保證高可用。
如果使用sequence對象,可以大大簡化ID的獲取邏輯,並更加安全。相關文章
- 【阿里雲資訊】性能大幅提升70%,阿里雲AliSQL邀請…
- 快訊:阿里雲正式開啟AliSQL邀測 開源版本為AliS…
- 【已更新內測入口】阿里雲宣布開放開源 AliSQL 數據…
推薦閱讀:
※關於高並發解決問題的一點總結
※從零開始實現資料庫系統(3)——並發控制
※劉寅:TiDB 工具鏈和生態
※資料庫(一)
※循序漸進學習如何在 MariaDB 中配置主從複製