{"article":{"slug":"tidesdb-now-available-for-mysql-v9-v26","title":"TidesDB now available for MySQL v9, v26","subtitle":null,"summary":"The TidesDB team announces TideSQL-MySQL v2.0.0, which loads the TidesDB LSM-tree storage engine into stock MySQL 9.7 and 26.7 as a plugin rather than a fork. The post covers per-table options via ENGINE_ATTRIBUTE, compression, value separation for large values, durability modes, TTL, encryption, optimistic MVCC, and sysbench comparisons against InnoDB.","content_type":"announcement","language":"en","canonical_url":"https://tidesdb.com/articles/tidesdb-now-available-for-mysql/","author":{"name":"Alex Gaetano Padula","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"TidesDB","url":"https://tidesdb.com/","listing_slug":null,"listing":null},"topics":[{"name":"Databases","slug":"databases","url":"https://listedarticles.com/topics/databases"},{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"},{"name":"Performance","slug":"performance","url":"https://listedarticles.com/topics/performance"},{"name":"Infrastructure","slug":"infrastructure","url":"https://listedarticles.com/topics/infrastructure"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1580,"reading_minutes":7,"published_at":"2026-10-05T00:00:00.000Z","added_at":"2026-10-08T02:11:20.928Z","updated_at":"2026-10-08T02:11:20.928Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/tidesdb-now-available-for-mysql-v9-v26","markdown_url":"https://listedarticles.com/articles/tidesdb-now-available-for-mysql-v9-v26.md","example":false,"citation":"Alex Gaetano Padula, TidesDB. \"TidesDB now available for MySQL v9, v26.\" 5 Oct 2026. https://tidesdb.com/articles/tidesdb-now-available-for-mysql/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://tidesdb.com/articles/tidesdb-now-available-for-mysql/"},"body_markdown":"# TidesDB now available for MySQL v9, v26\n\n*published on October 5th, 2026*\n\nPossibly most don’t know, but originally TideSQL started as a [MySQL fork](https://github.com/tidesdb/tidesql/tree/3ef20028f1a184112019b9767e7cd70e88667368) in which existed to implement the TidesDB library as a plugin, making its powerful storage engine available to MySQL users, but at the time the MySQL community had no way for us to make this possible, thus it’s original course changed.  Almost a year has passed and lots has changed since then. A [proposal](https://github.com/mysql/mysql-community/issues/82) was created for TidesDB to become available for MySQL and well, this has happened, TidesDB is [now available](https://github.com/tidesdb/tidesql-mysql) as an external plugin storage engine for MySQL with a wonderful set of features and more to come.\n\nCurrently the way to access the TidesDB plugin engine is through the `install.sh` script in the repository which you either point to your build or let the installer build the bundle for you.   Going down the line we’d like this to be easier for you the users and are in discussions with appropriate parties in regards to that.\n\nTidesDB is a write and space optimized storage engine which can keep up very well on reads.\n\nIt’s a plugin, not a fork. You run a stock MySQL and load the engine into it, and a TidesDB table can sit next to an InnoDB one in the same server. TideSQL-MySQL v2.0.0 is paired with TidesDB v10.1.1 and tested against MySQL v9.7.0 and v26.7.0.\n\nMySQL has no engine-specific `CREATE TABLE` grammar, so a table names its TidesDB options in `ENGINE_ATTRIBUTE`, a JSON object the server stores and hands to the engine without reading it.  The engine defines those names so the engine checks them, and a misspelled option fails the statement instead of being stored and ignored.  Every option has a `tidesdb_default_*` session variable behind it, so you can set the policy once and let every `CREATE TABLE` inherit it.  What a table does not name is resolved when it is created and stays with it.\n\nCompression is on by default.  The choices are `NONE`, `SNAPPY`, `LZ4`, `ZSTD` and `LZ4_FAST`, the default is `LZ4`, and `ZSTD` is there when you want the ratio more than the speed.\n\nLarge values stay out of compaction.  Each SSTable has its own key log and the whole database shares one segmented value log.  A value at or above `tidesdb_value_separation_threshold` goes to the value log with a pointer left in the key log, so every later merge rewrites the pointer and not the value.  That costs one value-log read per row on a scan, so a table you scan far more than you merge can set `{\"keep_values_inline\": true}` and keep every value in the key log whatever its size.\n\nThe shape of the LSM is yours to set.  `level_size_ratio` is how much larger each level is than the last, `min_levels` is the minimum depth, and `l1_file_count_trigger` is how many SSTables may gather at level 1 before compaction merges them down.  There is no compaction policy to choose, the engine picks among full preemptive merge, dividing merge and partitioned merge from the state of the tree.  For a table that deletes heavily, `tombstone_density_trigger` escalates compaction for a level-1 SSTable whose tombstones outgrow a ratio you set.\n\nDurability is one setting, `tidesdb_memtable_sync_mode`, and it governs every commit because every table shares the library’s write-ahead log.  `FULL` is the default and means a committed write has reached the device and survives power loss.  `INTERVAL` hands each commit to the operating system and reaches the device within the interval, so a process crash loses nothing and a machine crash loses at most that window.  `NONE` does nothing at commit time, so an acknowledged commit sits in a buffer until a later batch writes it out and a process crash loses it.  `NONE` is for data you can rebuild.\n\nRows can expire on their own, at the table level, the row level or the session level, and they resolve in that order. Encryption at rest is per row with a two-tier key arrangement, so rotating the master key touches no row data. An encrypted table’s rows are ciphertext by the time the library sees them, so compression is forced off for that column family, while its secondary indexes hold unencrypted comparable keys and keep whatever algorithm you picked.\n\nThe rest of what works today:\n\n- `FULLTEXT` indexes with natural-language and boolean modes, BM25 ranking, stop words and blend characters\n- foreign keys enforced inside the engine, `ON DELETE` and`ON UPDATE` with`CASCADE` ,`SET NULL` and`RESTRICT` , and self-references\n- spatial indexes, generated columns and JSON\n- vector columns store and read back, there is no similarity search over them\n- instant `ADD COLUMN` and`DROP COLUMN` , the packed row carries a self-describing header so the deserializer adapts to rows written under an earlier schema\n- statistics the optimizer can use without running `ANALYZE TABLE` , sampled the first time a populated table is asked for them\n- online backup and checkpoints, and `SHOW ENGINE TIDESDB STATUS` for what the tree is doing\n\nConcurrency is optimistic MVCC.  There are no pessimistic row locks, so there are no lock waits and no lock-wait deadlocks to tune.  A write conflict shows up at commit rather than inside the statement, as `ER_ERROR_DURING_COMMIT` (1180), and an application using explicit `BEGIN ... COMMIT` at `REPEATABLE READ` or higher should retry on it.  Autocommit statements run at `READ COMMITTED` where the library does no write-write checking, and a transaction that wrote nothing never conflicts.\n\nI ran [sysbench](https://github.com/akopytov/sysbench) 1.0.20 against both engines on the same server, 8 tables of 5 million rows, each engine on its own defaults.  The only two changes from stock are `READ COMMITTED` and the durability mode, matched across the two so neither gets a free pass on writes.  Transactions per second at 24 threads, which is where this box peaked.\n\nThe box:\n\n- Intel i9-13900, 24 threads online, 8 P-core (5.3-5.6 GHz) + 16 E-core (4.2 GHz)\n- the 8 P-cores isolated for the run\n- Ubuntu 24.04.4 LTS (6.8.0-136-generic)\n- 125.5 GiB DDR5, no ECC\n- NVMe Micron 7450, xfs, separate from the OS disk\n- gcc 13.3.0\n- jemalloc for library and server\n\n| workload | TidesDB | InnoDB |  |\n|---|---|---|---|\n| point select | 246,410 | 95,760 | 2.6x |\n| read write | 5,993 | 2,989 | 2.0x |\n| update index | 102,273 | 18,962 | 5.4x |\n| write only | 31,839 | 6,878 | 4.6x |\n| delete | 154,048 | 25,792 | 6.0x |\n\nThe bytes tell you why.  These come from `/proc/diskstats` rather than from either engine’s own accounting, so both are measured the same way below the engine, and each run is followed by a settle so writes an engine defers are still charged to the run that caused them.\n\n| workload | TidesDB | InnoDB |  |\n|---|---|---|---|\n| update index | 3,000 B | 56,461 B | 19x less |\n| write only | 9,290 B | 155,398 B | 17x less |\n| read write | 11,196 B | 192,741 B | 17x less |\n| delete | 916 B | 42,910 B | 47x less |\n\nBytes written to the device per operation.\n\nThe dataset is about 8 GB against the 256 MB block cache and 256 MB memtable TidesDB ships with, and InnoDB’s 128 MB buffer pool, so neither engine is holding it in memory. An InnoDB update of a random row has to find a 16 KB page that is usually not resident, read it, change it and write all 16 KB back through the doublewrite buffer with redo on top. TidesDB appends a few hundred bytes and sorts it out later.\n\nNothing in the standard sysbench set touches the value log.  An `sbtest` row is about 188 bytes and a value goes to the log above 1024, so every workload above stays in the key logs.  Large values are the case the design is for, so I ran a separate script against 4 tables of 200,000 rows with a 4 KB text column, built out of a small vocabulary of field names and words so it compresses the way a log line or a JSON document does rather than the way random digits do.  Three configurations, the third being TidesDB with `keep_values_inline` set, which holds every value in the key log whatever its size.  That one is a control.  Without it a gap between TidesDB and InnoDB could be anything about the engine; with it the value log is the only thing that changed.\n\n| 4 KB values | TidesDB | TidesDB inline | InnoDB |\n|---|---|---|---|\n| insert | 100,613 | 32,330 | 32,562 |\n| select | 255,606 | 74,996 | 98,979 |\n\nInline and InnoDB land on the same insert number, and value separation is three times both. So the gain on large writes is not that this is an LSM, it is that compaction rewrites the keys and leaves the values where they are. The reads say it from the other side. A 4 KB value held in the key log means few rows to a page, and while inline has the fastest median read of the three at 0.07 ms, its average is 0.38 ms and its worst case is over 40 ms, because compaction is hauling those values around underneath the reads. The default averages 0.09 ms with a worst case of 6 ms.\n\nThe same 3.05 GiB of payload lands as 4.24 GiB on InnoDB and 1.19 GiB on TidesDB, which is LZ4 at defaults on data that compresses like text. Loading it took 17 seconds against 8.\n\nThat’s all for this article, do give TideSQL for MySQL a try!\n\n—\n\nRaw data and scripts: [data.zip](https://tidesdb.com/tidesql-mysql-v2-0-0/data.zip) (sha256: 95fec38d5b14dc9673f1c5b3351f0cb0f469c8c7a6c584428b064c8a1a96fa2e)\n","body_html":"<h1 id=\"tidesdb-now-available-for-mysql-v9-v26\">TidesDB now available for MySQL v9, v26</h1>\n<p><em>published on October 5th, 2026</em></p>\n<p>Possibly most don’t know, but originally TideSQL started as a <a href=\"https://github.com/tidesdb/tidesql/tree/3ef20028f1a184112019b9767e7cd70e88667368\" rel=\"nofollow ugc noopener\">MySQL fork</a> in which existed to implement the TidesDB library as a plugin, making its powerful storage engine available to MySQL users, but at the time the MySQL community had no way for us to make this possible, thus it’s original course changed.  Almost a year has passed and lots has changed since then. A <a href=\"https://github.com/mysql/mysql-community/issues/82\" rel=\"nofollow ugc noopener\">proposal</a> was created for TidesDB to become available for MySQL and well, this has happened, TidesDB is <a href=\"https://github.com/tidesdb/tidesql-mysql\" rel=\"nofollow ugc noopener\">now available</a> as an external plugin storage engine for MySQL with a wonderful set of features and more to come.</p>\n<p>Currently the way to access the TidesDB plugin engine is through the <code>install.sh</code> script in the repository which you either point to your build or let the installer build the bundle for you.   Going down the line we’d like this to be easier for you the users and are in discussions with appropriate parties in regards to that.</p>\n<p>TidesDB is a write and space optimized storage engine which can keep up very well on reads.</p>\n<p>It’s a plugin, not a fork. You run a stock MySQL and load the engine into it, and a TidesDB table can sit next to an InnoDB one in the same server. TideSQL-MySQL v2.0.0 is paired with TidesDB v10.1.1 and tested against MySQL v9.7.0 and v26.7.0.</p>\n<p>MySQL has no engine-specific <code>CREATE TABLE</code> grammar, so a table names its TidesDB options in <code>ENGINE_ATTRIBUTE</code>, a JSON object the server stores and hands to the engine without reading it.  The engine defines those names so the engine checks them, and a misspelled option fails the statement instead of being stored and ignored.  Every option has a <code>tidesdb_default_*</code> session variable behind it, so you can set the policy once and let every <code>CREATE TABLE</code> inherit it.  What a table does not name is resolved when it is created and stays with it.</p>\n<p>Compression is on by default.  The choices are <code>NONE</code>, <code>SNAPPY</code>, <code>LZ4</code>, <code>ZSTD</code> and <code>LZ4_FAST</code>, the default is <code>LZ4</code>, and <code>ZSTD</code> is there when you want the ratio more than the speed.</p>\n<p>Large values stay out of compaction.  Each SSTable has its own key log and the whole database shares one segmented value log.  A value at or above <code>tidesdb_value_separation_threshold</code> goes to the value log with a pointer left in the key log, so every later merge rewrites the pointer and not the value.  That costs one value-log read per row on a scan, so a table you scan far more than you merge can set <code>{&quot;keep_values_inline&quot;: true}</code> and keep every value in the key log whatever its size.</p>\n<p>The shape of the LSM is yours to set.  <code>level_size_ratio</code> is how much larger each level is than the last, <code>min_levels</code> is the minimum depth, and <code>l1_file_count_trigger</code> is how many SSTables may gather at level 1 before compaction merges them down.  There is no compaction policy to choose, the engine picks among full preemptive merge, dividing merge and partitioned merge from the state of the tree.  For a table that deletes heavily, <code>tombstone_density_trigger</code> escalates compaction for a level-1 SSTable whose tombstones outgrow a ratio you set.</p>\n<p>Durability is one setting, <code>tidesdb_memtable_sync_mode</code>, and it governs every commit because every table shares the library’s write-ahead log.  <code>FULL</code> is the default and means a committed write has reached the device and survives power loss.  <code>INTERVAL</code> hands each commit to the operating system and reaches the device within the interval, so a process crash loses nothing and a machine crash loses at most that window.  <code>NONE</code> does nothing at commit time, so an acknowledged commit sits in a buffer until a later batch writes it out and a process crash loses it.  <code>NONE</code> is for data you can rebuild.</p>\n<p>Rows can expire on their own, at the table level, the row level or the session level, and they resolve in that order. Encryption at rest is per row with a two-tier key arrangement, so rotating the master key touches no row data. An encrypted table’s rows are ciphertext by the time the library sees them, so compression is forced off for that column family, while its secondary indexes hold unencrypted comparable keys and keep whatever algorithm you picked.</p>\n<p>The rest of what works today:</p>\n<ul><li><code>FULLTEXT</code> indexes with natural-language and boolean modes, BM25 ranking, stop words and blend characters</li><li>foreign keys enforced inside the engine, <code>ON DELETE</code> and<code>ON UPDATE</code> with<code>CASCADE</code> ,<code>SET NULL</code> and<code>RESTRICT</code> , and self-references</li><li>spatial indexes, generated columns and JSON</li><li>vector columns store and read back, there is no similarity search over them</li><li>instant <code>ADD COLUMN</code> and<code>DROP COLUMN</code> , the packed row carries a self-describing header so the deserializer adapts to rows written under an earlier schema</li><li>statistics the optimizer can use without running <code>ANALYZE TABLE</code> , sampled the first time a populated table is asked for them</li><li>online backup and checkpoints, and <code>SHOW ENGINE TIDESDB STATUS</code> for what the tree is doing</li></ul>\n<p>Concurrency is optimistic MVCC.  There are no pessimistic row locks, so there are no lock waits and no lock-wait deadlocks to tune.  A write conflict shows up at commit rather than inside the statement, as <code>ER_ERROR_DURING_COMMIT</code> (1180), and an application using explicit <code>BEGIN ... COMMIT</code> at <code>REPEATABLE READ</code> or higher should retry on it.  Autocommit statements run at <code>READ COMMITTED</code> where the library does no write-write checking, and a transaction that wrote nothing never conflicts.</p>\n<p>I ran <a href=\"https://github.com/akopytov/sysbench\" rel=\"nofollow ugc noopener\">sysbench</a> 1.0.20 against both engines on the same server, 8 tables of 5 million rows, each engine on its own defaults.  The only two changes from stock are <code>READ COMMITTED</code> and the durability mode, matched across the two so neither gets a free pass on writes.  Transactions per second at 24 threads, which is where this box peaked.</p>\n<p>The box:</p>\n<ul><li>Intel i9-13900, 24 threads online, 8 P-core (5.3-5.6 GHz) + 16 E-core (4.2 GHz)</li><li>the 8 P-cores isolated for the run</li><li>Ubuntu 24.04.4 LTS (6.8.0-136-generic)</li><li>125.5 GiB DDR5, no ECC</li><li>NVMe Micron 7450, xfs, separate from the OS disk</li><li>gcc 13.3.0</li><li>jemalloc for library and server</li></ul>\n<div class=\"table-wrap\"><table><thead><tr><th>workload</th><th>TidesDB</th><th>InnoDB</th><th></th></tr></thead><tbody><tr><td>point select</td><td>246,410</td><td>95,760</td><td>2.6x</td></tr><tr><td>read write</td><td>5,993</td><td>2,989</td><td>2.0x</td></tr><tr><td>update index</td><td>102,273</td><td>18,962</td><td>5.4x</td></tr><tr><td>write only</td><td>31,839</td><td>6,878</td><td>4.6x</td></tr><tr><td>delete</td><td>154,048</td><td>25,792</td><td>6.0x</td></tr></tbody></table></div>\n<p>The bytes tell you why.  These come from <code>/proc/diskstats</code> rather than from either engine’s own accounting, so both are measured the same way below the engine, and each run is followed by a settle so writes an engine defers are still charged to the run that caused them.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>workload</th><th>TidesDB</th><th>InnoDB</th><th></th></tr></thead><tbody><tr><td>update index</td><td>3,000 B</td><td>56,461 B</td><td>19x less</td></tr><tr><td>write only</td><td>9,290 B</td><td>155,398 B</td><td>17x less</td></tr><tr><td>read write</td><td>11,196 B</td><td>192,741 B</td><td>17x less</td></tr><tr><td>delete</td><td>916 B</td><td>42,910 B</td><td>47x less</td></tr></tbody></table></div>\n<p>Bytes written to the device per operation.</p>\n<p>The dataset is about 8 GB against the 256 MB block cache and 256 MB memtable TidesDB ships with, and InnoDB’s 128 MB buffer pool, so neither engine is holding it in memory. An InnoDB update of a random row has to find a 16 KB page that is usually not resident, read it, change it and write all 16 KB back through the doublewrite buffer with redo on top. TidesDB appends a few hundred bytes and sorts it out later.</p>\n<p>Nothing in the standard sysbench set touches the value log.  An <code>sbtest</code> row is about 188 bytes and a value goes to the log above 1024, so every workload above stays in the key logs.  Large values are the case the design is for, so I ran a separate script against 4 tables of 200,000 rows with a 4 KB text column, built out of a small vocabulary of field names and words so it compresses the way a log line or a JSON document does rather than the way random digits do.  Three configurations, the third being TidesDB with <code>keep_values_inline</code> set, which holds every value in the key log whatever its size.  That one is a control.  Without it a gap between TidesDB and InnoDB could be anything about the engine; with it the value log is the only thing that changed.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>4 KB values</th><th>TidesDB</th><th>TidesDB inline</th><th>InnoDB</th></tr></thead><tbody><tr><td>insert</td><td>100,613</td><td>32,330</td><td>32,562</td></tr><tr><td>select</td><td>255,606</td><td>74,996</td><td>98,979</td></tr></tbody></table></div>\n<p>Inline and InnoDB land on the same insert number, and value separation is three times both. So the gain on large writes is not that this is an LSM, it is that compaction rewrites the keys and leaves the values where they are. The reads say it from the other side. A 4 KB value held in the key log means few rows to a page, and while inline has the fastest median read of the three at 0.07 ms, its average is 0.38 ms and its worst case is over 40 ms, because compaction is hauling those values around underneath the reads. The default averages 0.09 ms with a worst case of 6 ms.</p>\n<p>The same 3.05 GiB of payload lands as 4.24 GiB on InnoDB and 1.19 GiB on TidesDB, which is LZ4 at defaults on data that compresses like text. Loading it took 17 seconds against 8.</p>\n<p>That’s all for this article, do give TideSQL for MySQL a try!</p>\n<p>—</p>\n<p>Raw data and scripts: <a href=\"https://tidesdb.com/tidesql-mysql-v2-0-0/data.zip\" rel=\"nofollow ugc noopener\">data.zip</a> (sha256: 95fec38d5b14dc9673f1c5b3351f0cb0f469c8c7a6c584428b064c8a1a96fa2e)</p>","headings":[{"level":1,"text":"TidesDB now available for MySQL v9, v26","id":"tidesdb-now-available-for-mysql-v9-v26"}]}}